keycloak-memoizeit

Details

diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
new file mode 100755
index 0000000..ee344b8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -0,0 +1,260 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.services.resources;
+
+import org.jboss.resteasy.jose.jws.JWSInput;
+import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.AccessCodeEntry;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.TokenManager;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.services.resources.flows.FormFlows;
+import org.keycloak.services.validation.Validation;
+import org.picketlink.idm.credential.util.TimeBasedOTP;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.*;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.Providers;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RequiredActionsService {
+
+    private RealmModel realm;
+
+    @Context
+    private HttpRequest request;
+
+    @Context
+    protected HttpHeaders headers;
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    protected Providers providers;
+
+    protected AuthenticationManager authManager = new AuthenticationManager();
+
+    private TokenManager tokenManager;
+
+    public RequiredActionsService(RealmModel realm, TokenManager tokenManager) {
+        this.realm = realm;
+        this.tokenManager = tokenManager;
+    }
+
+    @Path("")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response updateProfile(final MultivaluedMap<String, String> formData) {
+        AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PROFILE);
+        if (accessCode == null) {
+            return forwardToErrorPage();
+        }
+
+        UserModel user = getUser(accessCode);
+        user.setFirstName(formData.getFirst("firstName"));
+        user.setLastName(formData.getFirst("lastName"));
+        user.setEmail(formData.getFirst("email"));
+
+        user.removeRequiredAction(RequiredAction.UPDATE_PROFILE);
+        accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PROFILE);
+
+        return redirectOauth(user, accessCode);
+    }
+
+    @Path("totp")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response configureTotp(final MultivaluedMap<String, String> formData) {
+        AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.CONFIGURE_TOTP);
+        if (accessCode == null) {
+            return forwardToErrorPage();
+        }
+
+        UserModel user = getUser(accessCode);
+
+        String totp = formData.getFirst("totp");
+        String totpSecret = formData.getFirst("totpSecret");
+
+        FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
+        if (Validation.isEmpty(totp)) {
+            return forms.setError(Messages.MISSING_TOTP).forwardToAction(RequiredAction.CONFIGURE_TOTP);
+        } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
+            return forms.setError(Messages.INVALID_TOTP).forwardToAction(RequiredAction.CONFIGURE_TOTP);
+        }
+
+        UserCredentialModel credentials = new UserCredentialModel();
+        credentials.setType(CredentialRepresentation.TOTP);
+        credentials.setValue(formData.getFirst("totpSecret"));
+        realm.updateCredential(user, credentials);
+
+        user.setTotp(true);
+
+        user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
+        accessCode.getRequiredActions().remove(RequiredAction.CONFIGURE_TOTP);
+
+        return redirectOauth(user, accessCode);
+    }
+
+
+    @Path("email-verification")
+    @GET
+    public Response emailVerification() {
+        if (uriInfo.getQueryParameters().containsKey("key")) {
+            AccessCodeEntry accessCode = tokenManager.getAccessCode(uriInfo.getQueryParameters().getFirst("key"));
+            if (accessCode == null || accessCode.isExpired()
+                    || !accessCode.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL)) {
+                return forwardToErrorPage();
+            }
+
+            UserModel user = getUser(accessCode);
+            user.setEmailVerified(true);
+
+            user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
+            accessCode.getRequiredActions().remove(RequiredAction.VERIFY_EMAIL);
+
+            return redirectOauth(user, accessCode);
+        } else {
+            AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.VERIFY_EMAIL);
+            if (accessCode == null) {
+                return forwardToErrorPage();
+            }
+
+            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(accessCode.getUser())
+                    .forwardToAction(RequiredAction.VERIFY_EMAIL);
+        }
+    }
+
+    @Path("password")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response resetPassword(final MultivaluedMap<String, String> formData) {
+        AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PASSWORD);
+        if (accessCode == null) {
+            return forwardToErrorPage();
+        }
+
+        UserModel user = getUser(accessCode);
+
+        String password = formData.getFirst("password");
+        String passwordNew = formData.getFirst("password-new");
+        String passwordConfirm = formData.getFirst("password-confirm");
+
+        FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
+        if (Validation.isEmpty(passwordNew)) {
+            forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+        } else if (!passwordNew.equals(passwordConfirm)) {
+            forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+        }
+
+        UserCredentialModel credentials = new UserCredentialModel();
+        credentials.setType(CredentialRepresentation.PASSWORD);
+        credentials.setValue(passwordNew);
+
+        realm.updateCredential(user, credentials);
+
+        user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
+        if (accessCode != null) {
+            accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PASSWORD);
+        }
+
+        if (accessCode != null) {
+            return redirectOauth(user, accessCode);
+        } else {
+            return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
+        }
+    }
+
+    private AccessCodeEntry getAccessCodeEntry(RequiredAction requiredAction) {
+        String code = uriInfo.getQueryParameters().getFirst(FormFlows.CODE);
+        if (code == null) {
+            return null;
+        }
+
+        JWSInput input = new JWSInput(code, providers);
+        boolean verifiedCode = false;
+        try {
+            verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
+        } catch (Exception ignored) {
+            return null;
+        }
+
+        if (!verifiedCode) {
+            return null;
+        }
+
+        String key = input.readContent(String.class);
+        AccessCodeEntry accessCodeEntry = tokenManager.getAccessCode(key);
+        if (accessCodeEntry == null) {
+            return null;
+        }
+
+        if (accessCodeEntry.isExpired()) {
+            return null;
+        }
+
+        if (accessCodeEntry.getRequiredActions() == null || !accessCodeEntry.getRequiredActions().contains(requiredAction)) {
+            return null;
+        }
+
+        return accessCodeEntry;
+    }
+
+    private UserModel getUser(AccessCodeEntry accessCode) {
+        return realm.getUser(accessCode.getUser().getLoginName());
+    }
+
+    private Response redirectOauth(UserModel user, AccessCodeEntry accessCode) {
+        if (accessCode == null) {
+            return null;
+        }
+
+        Set<RequiredAction> requiredActions = user.getRequiredActions();
+        if (!requiredActions.isEmpty()) {
+            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(user)
+                    .forwardToAction(requiredActions.iterator().next());
+        } else {
+            accessCode.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
+            return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
+                    accessCode.getState(), accessCode.getRedirectUri());
+        }
+    }
+
+    private Response forwardToErrorPage() {
+        return Flows.forms(realm, request, uriInfo).forwardToErrorPage();
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 89742bf..c8bfd0e 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -217,6 +217,11 @@ public class TokenService {
         }
     }
 
+    @Path("auth/request/login-actions")
+    public RequiredActionsService getRequiredActionsService() {
+        return new RequiredActionsService(realm, tokenManager);
+    }
+
     private void isTotpConfigurationRequired(UserModel user) {
         for (RequiredCredentialModel c : realm.getRequiredCredentials()) {
             if (c.getType().equals(CredentialRepresentation.TOTP) && !user.isTotp()) {