<html>
  <head>
    <meta content="text/html; charset=windows-1252"
      http-equiv="Content-Type">
  </head>
  <body bgcolor="#FFFFFF" text="#000000">
    <div class="moz-cite-prefix">There was already some quite a long
      discussion about similar stuff. It was about consents though, not
      about required actions. However IMO both consents and
      requiredActions are quite similar thing and it's by design that
      user is not able to login with directGrant if he has either
      consent or requiredAction on himself.<br>
      <br>
      However when I look at your particular use-case, it seems that you
      are using LoginStatsRecordingRequiredActionProvider as an
      interceptor, which doesn't need any real requiredAction on user,
      but you just want to ensure that "evaluateTriggers" is called
      after each user login. Is it correct? <br>
      <br>
      Then you can just check your requiredAction provider as "enabled",
      but NOT as "default" . Note that "evaluateTriggers" will be always
      invoked for every user after his login even if user doesn't have
      the particular action on him. The purpose of "evaluateTriggers" is
      actually to check, if requiredAction should be added to the user
      if some specific conditions occurs. For example see <span
        style="background-color:#e4e4ff;">VerifyEmail.</span><span
        style="background-color:#e4e4ff;">evaluateTriggers</span><span
        style="background-color:#e4e4ff;"></span> , which adds the
      VERIFY_EMAIL requiredAction to user if he doesn't yet have
      verified email.<br>
      <br>
      Marek<br>
      <meta http-equiv="content-type" content="text/html;
        charset=windows-1252">
      <meta http-equiv="content-type" content="text/html;
        charset=windows-1252">
      <br>
      On 08/07/16 10:35, Thomas Darimont wrote:<br>
    </div>
    <blockquote
cite="mid:CAK-7U1hA1OBzH9Bj3gufT8ztOmr8vZ-JKOsAzKV=H3_gjdLeRA@mail.gmail.com"
      type="cite">
      <div dir="ltr">
        <div>Hello Group,</div>
        <div><br>
        </div>
        <div>I just found out that as long as a user has a
          required_action set up one cannot authenticate via grant_type
          password throught the TokenEndpoint </div>
        <div>because of this line in
          TokenEndpoint.buildResourceOwnerPasswordCredentialsGrant</div>
        <div><br>
        </div>
        <div>if (user.getRequiredActions() != null &amp;&amp;
          user.getRequiredActions().size() &gt; 0) {</div>
        <div>   event.error(Errors.RESOLVE_REQUIRED_ACTIONS);</div>
        <div>   throw new ErrorResponseException("invalid_grant",
          "Account is not fully set up", Response.Status.BAD_REQUEST);</div>
        <div>}</div>
        <div><br>
        </div>
        <div>current master:</div>
        <div><a moz-do-not-send="true"
href="https://github.com/keycloak/keycloak/blob/bd2887aa77184d82e795e4200eb55a3d9b11e4d4/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java#L388">https://github.com/keycloak/keycloak/blob/bd2887aa77184d82e795e4200eb55a3d9b11e4d4/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java#L388</a></div>
        <div><br>
        </div>
        <div>1.9.x</div>
        <div><a moz-do-not-send="true"
href="https://github.com/keycloak/keycloak/blob/e7822431fded5948a5e248766e6ffbf86d476cf8/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java#L388">https://github.com/keycloak/keycloak/blob/e7822431fded5948a5e248766e6ffbf86d476cf8/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java#L388</a></div>
        <div><br>
        </div>
        <div>I think that this is too restrictive. Imagine a default
          required action that has no user interaction but records some
          user login statistics like </div>
        <div>last login date, count logins, login failures etc.</div>
        <div>In this case it would be better to check if the configured
          required_action requires user-interaction (e.g. via a new
          method boolean isInteractive())</div>
        <div>or communicate that required actions should not be used for
          actions that don't require user interactions - an
          authenticator is then probably the better choice.</div>
        <div><br>
        </div>
        <div>Cheers,</div>
        <div>Thomas</div>
        <div><br>
        </div>
        <div>I tested this with a required action like the
          LoginStatsRecordingRequiredActionProvider listed below.</div>
        <div>Now setup a client with the name test-client and a user
          with the name tester.</div>
        <div><br>
        </div>
        <div>With that deployed to keycloak as a default action one can
          use the following curl command</div>
        <div>to see the problem.</div>
        <div><br>
        </div>
        <div>KC_REALM=test-realm</div>
        <div>KC_USERNAME=tester</div>
        <div>KC_PASSWORD=test</div>
        <div>KC_CLIENT=test-client</div>
        <div>KC_CLIENT_SECRET=e9ca8fad-d399-4455-5290-817102e7f9ff</div>
        <div>KC_SERVER=<a moz-do-not-send="true"
            href="http://192.168.99.1:8080">192.168.99.1:8080</a></div>
        <div>KC_CONTEXT=auth</div>
        <div><br>
        </div>
        <div>curl -k -v --noproxy 192.168.99.1 \</div>
        <div>        -H "Content-Type:
          application/x-www-form-urlencoded" \</div>
        <div>        -d "grant_type=password" \</div>
        <div>        -d "username=$KC_USERNAME" \</div>
        <div>        -d "password=$KC_PASSWORD" \</div>
        <div>        -d "client_id=$KC_CLIENT" \</div>
        <div>        -d "client_secret=$KC_CLIENT_SECRET" \</div>
        <div>       
<a class="moz-txt-link-rfc2396E" href="http://$KC_SERVER/$KC_CONTEXT/realms/$KC_REALM/protocol/openid-connect/token">"http://$KC_SERVER/$KC_CONTEXT/realms/$KC_REALM/protocol/openid-connect/token"</a></div>
        <div><br>
        </div>
        <div>Gives:</div>
        <div>&lt; HTTP/1.1 400 Bad Request</div>
        <div>&lt; Connection: keep-alive</div>
        <div>&lt; X-Powered-By: Undertow/1</div>
        <div>&lt; Server: WildFly/10</div>
        <div>&lt; Content-Type: application/json</div>
        <div>&lt; Content-Length: 75</div>
        <div>&lt; Date: Fri, 08 Jul 2016 08:24:59 GMT</div>
        <div>{"error_description":"Account is not fully set
          up","error":"invalid_grant"}</div>
        <div><br>
        </div>
        <div><br>
        </div>
        <div>----</div>
        <div><br>
        </div>
        <div><br>
        </div>
        <div>package com.acme.keycloak.ext.authentication;</div>
        <div><br>
        </div>
        <div>import static java.util.Arrays.asList;</div>
        <div><br>
        </div>
        <div>import java.time.Instant;</div>
        <div>import java.time.LocalDateTime;</div>
        <div>import java.util.Arrays;</div>
        <div>import java.util.List;</div>
        <div>import java.util.TimeZone;</div>
        <div><br>
        </div>
        <div>import org.jboss.logging.Logger;</div>
        <div>import org.keycloak.Config.Scope;</div>
        <div>import org.keycloak.authentication.RequiredActionContext;</div>
        <div>import org.keycloak.authentication.RequiredActionFactory;</div>
        <div>import org.keycloak.authentication.RequiredActionProvider;</div>
        <div>import org.keycloak.models.KeycloakSession;</div>
        <div>import org.keycloak.models.KeycloakSessionFactory;</div>
        <div>import org.keycloak.models.UserLoginFailureModel;</div>
        <div>import org.keycloak.models.UserModel;</div>
        <div><br>
        </div>
        <div>/**</div>
        <div> * Collects information about User Login behaviour.</div>
        <div> * &lt;p&gt;</div>
        <div> * Collects the following:</div>
        <div> * &lt;ul&gt;</div>
        <div> * &lt;li&gt;Date / Time of first login&lt;/li&gt;</div>
        <div> * &lt;li&gt;Date / Time of recent login&lt;/li&gt;</div>
        <div> * &lt;li&gt;Count of logins since first login&lt;/li&gt;</div>
        <div> * &lt;li&gt;Count of failed logins since the configured
          &lt;code&gt;Failure Reset Time&lt;/code&gt; unter
          &lt;code&gt;Security Defenses&lt;/code&gt;&lt;/li&gt;</div>
        <div> * &lt;/ul&gt;</div>
        <div> * &lt;p&gt;</div>
        <div> * This {@link RequiredActionProvider} is Stateless and can
          be reused.</div>
        <div> * </div>
        <div> * @author tdarimont</div>
        <div> */</div>
        <div>public class LoginStatsRecordingRequiredActionProvider
          implements RequiredActionProvider, RequiredActionFactory {</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>private
          static final Logger LOG =
          Logger.getLogger(LoginStatsRecordingRequiredActionProvider.class);</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>private
          static final String PROVIDER_ID = "login_stats_action";</div>
        <div><span class="" style="white-space:pre">        </span>private
          static final String RECORD_LOGIN_STATISTICS_ACTION = "Record
          Login Statistics Action";</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>private
          static final String LOGIN_LOGIN_COUNT = "login.login-count";</div>
        <div><span class="" style="white-space:pre">        </span>private
          static final String LOGIN_FAILED_LOGIN_COUNT =
          "login.failed-login-count";</div>
        <div><span class="" style="white-space:pre">        </span>private
          static final String LOGIN_FAILED_LOGIN_DATE =
          "login.failed-login-date";</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>private
          static final String LOGIN_FIRST_LOGIN_DATE =
          "login.first-login-date";</div>
        <div><span class="" style="white-space:pre">        </span>private
          static final String LOGIN_RECENT_LOGIN_DATE =
          "login.recent-login-date";</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>private
          static final String ONE = "1";</div>
        <div><span class="" style="white-space:pre">        </span>private
          static final String ZERO = "0";</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>private
          static final LoginStatsRecordingRequiredActionProvider
          INSTANCE = new LoginStatsRecordingRequiredActionProvider();</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>@Override</div>
        <div><span class="" style="white-space:pre">        </span>public void
          close() {</div>
        <div><span class="" style="white-space:pre">                </span>// NOOP</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>@Override</div>
        <div><span class="" style="white-space:pre">        </span>public void
          evaluateTriggers(RequiredActionContext context) {</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>UserModel
          user = context.getUser();</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>try {</div>
        <div><span class="" style="white-space:pre">                        </span>recordFirstLogin(user);</div>
        <div><span class="" style="white-space:pre">                </span>} catch
          (Exception ex) {</div>
        <div><span class="" style="white-space:pre">                        </span>LOG.warnv(ex,
          "Couldn't record first login &lt;{0}&gt;", this);</div>
        <div><span class="" style="white-space:pre">                </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>try {</div>
        <div><span class="" style="white-space:pre">                        </span>recordRecentLogin(user);</div>
        <div><span class="" style="white-space:pre">                </span>} catch
          (Exception ex) {</div>
        <div><span class="" style="white-space:pre">                        </span>LOG.warnv(ex,
          "Couldn't record recent login &lt;{0}&gt;", this);</div>
        <div><span class="" style="white-space:pre">                </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>try {</div>
        <div><span class="" style="white-space:pre">                        </span>recordLoginCount(user);</div>
        <div><span class="" style="white-space:pre">                </span>} catch
          (Exception ex) {</div>
        <div><span class="" style="white-space:pre">                        </span>LOG.warnv(ex,
          "Couldn't record login count &lt;{0}&gt;", this);</div>
        <div><span class="" style="white-space:pre">                </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>try {</div>
        <div><span class="" style="white-space:pre">                        </span>recordFailedLogin(user,
          context);</div>
        <div><span class="" style="white-space:pre">                </span>} catch
          (Exception ex) {</div>
        <div><span class="" style="white-space:pre">                        </span>LOG.warnv(ex,
          "Couldn't record failed login &lt;{0}&gt;", this);</div>
        <div><span class="" style="white-space:pre">                </span>}</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>private void
          recordFailedLogin(UserModel user, RequiredActionContext
          context) {</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>UserLoginFailureModel
          loginFailures = context.getSession().sessions()</div>
        <div><span class="" style="white-space:pre">                                </span>.getUserLoginFailure(context.getRealm(),
          user.getUsername());</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>if
          (loginFailures != null) {</div>
        <div><span class="" style="white-space:pre">                        </span>user.setAttribute(LOGIN_FAILED_LOGIN_COUNT,
Arrays.asList(String.valueOf(loginFailures.getNumFailures())));</div>
        <div><span class="" style="white-space:pre">                        </span>user.setAttribute(LOGIN_FAILED_LOGIN_DATE,</div>
        <div><span class="" style="white-space:pre">                                        </span>Arrays.asList(getLocalDateTimeFromTimestamp(loginFailures.getLastFailure()).toString()));</div>
        <div><span class="" style="white-space:pre">                </span>} else {</div>
        <div><span class="" style="white-space:pre">                        </span>user.setAttribute(LOGIN_FAILED_LOGIN_COUNT,
          Arrays.asList(ZERO));</div>
        <div><span class="" style="white-space:pre">                </span>}</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>private void
          recordLoginCount(UserModel user) {</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>List&lt;String&gt;
          list = user.getAttribute(LOGIN_LOGIN_COUNT);</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>if (list ==
          null || list.isEmpty()) {</div>
        <div><span class="" style="white-space:pre">                        </span>list =
          asList(ONE);</div>
        <div><span class="" style="white-space:pre">                </span>} else {</div>
        <div><span class="" style="white-space:pre">                        </span>list =
          asList(String.valueOf(Long.parseLong(list.get(0)) + 1));</div>
        <div><span class="" style="white-space:pre">                </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>user.setAttribute(LOGIN_LOGIN_COUNT,
          list);</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>private void
          recordRecentLogin(UserModel user) {</div>
        <div><span class="" style="white-space:pre">                </span>user.setAttribute(LOGIN_RECENT_LOGIN_DATE,</div>
        <div><span class="" style="white-space:pre">                                </span>asList(getLocalDateTimeFromTimestamp(System.currentTimeMillis()).toString()));</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>private void
          recordFirstLogin(UserModel user) {</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>List&lt;String&gt;
          list = user.getAttribute(LOGIN_FIRST_LOGIN_DATE);</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">                </span>if (list ==
          null || list.isEmpty()) {</div>
        <div><span class="" style="white-space:pre">                        </span>user.setAttribute(LOGIN_FIRST_LOGIN_DATE,</div>
        <div><span class="" style="white-space:pre">                                        </span>asList(getLocalDateTimeFromTimestamp(System.currentTimeMillis()).toString()));</div>
        <div><span class="" style="white-space:pre">                </span>}</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>@Override</div>
        <div><span class="" style="white-space:pre">        </span>public void
          requiredActionChallenge(RequiredActionContext context) {</div>
        <div><span class="" style="white-space:pre">                </span>// NOOP</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>@Override</div>
        <div><span class="" style="white-space:pre">        </span>public void
          processAction(RequiredActionContext context) {</div>
        <div><span class="" style="white-space:pre">                </span>context.success();</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>@Override</div>
        <div><span class="" style="white-space:pre">        </span>public
          RequiredActionProvider create(KeycloakSession session) {</div>
        <div><span class="" style="white-space:pre">                </span>return
          INSTANCE;</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>@Override</div>
        <div><span class="" style="white-space:pre">        </span>public void
          init(Scope config) {</div>
        <div><span class="" style="white-space:pre">                </span>LOG.infov("Creating
          IdM Keycloak extension &lt;{0}&gt;", this);</div>
        <div><span class="" style="white-space:pre">                </span>// NOOP</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>@Override</div>
        <div><span class="" style="white-space:pre">        </span>public void
          postInit(KeycloakSessionFactory factory) {</div>
        <div><span class="" style="white-space:pre">                </span>// NOOP</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>@Override</div>
        <div><span class="" style="white-space:pre">        </span>public
          String getId() {</div>
        <div><span class="" style="white-space:pre">                </span>return
          PROVIDER_ID;</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>@Override</div>
        <div><span class="" style="white-space:pre">        </span>public
          String getDisplayText() {</div>
        <div><span class="" style="white-space:pre">                </span>return
          RECORD_LOGIN_STATISTICS_ACTION;</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div><br>
        </div>
        <div><span class="" style="white-space:pre">        </span>private
          LocalDateTime getLocalDateTimeFromTimestamp(long
          timestampMillis) {</div>
        <div><span class="" style="white-space:pre">                </span>return
          LocalDateTime.ofInstant(Instant.ofEpochSecond(timestampMillis
          / 1000), TimeZone.getDefault().toZoneId());</div>
        <div><span class="" style="white-space:pre">        </span>}</div>
        <div>}</div>
        <div><br>
        </div>
      </div>
      <br>
      <fieldset class="mimeAttachmentHeader"></fieldset>
      <br>
      <pre wrap="">_______________________________________________
keycloak-dev mailing list
<a class="moz-txt-link-abbreviated" href="mailto:keycloak-dev@lists.jboss.org">keycloak-dev@lists.jboss.org</a>
<a class="moz-txt-link-freetext" href="https://lists.jboss.org/mailman/listinfo/keycloak-dev">https://lists.jboss.org/mailman/listinfo/keycloak-dev</a></pre>
    </blockquote>
    <br>
  </body>
</html>