From c268785e173791b31c8f422737e24da7bba72e4d Mon Sep 17 00:00:00 2001 From: shriharsh Date: Wed, 15 Nov 2017 11:03:12 +0530 Subject: [PATCH 1/4] Issue #14 feature: Login using phone number --- .../phone/AbstractPhoneFormAuthenticator.java | 211 ++++++++++++++++++ .../login/phone/PhonePasswordForm.java | 112 ++++++++++ .../login/phone/PhonePasswordFormFactory.java | 108 +++++++++ .../sms}/KeycloakSmsAuthenticator.java | 2 +- .../KeycloakSmsAuthenticatorConstants.java | 4 +- ...oakSmsAuthenticatorCredentialProvider.java | 2 +- ...uthenticatorCredentialProviderFactory.java | 2 +- .../sms}/KeycloakSmsAuthenticatorFactory.java | 2 +- .../sms}/KeycloakSmsAuthenticatorUtil.java | 2 +- ...ycloak.authentication.AuthenticatorFactory | 3 +- ...cloak.credential.CredentialProviderFactory | 2 +- 11 files changed, 441 insertions(+), 9 deletions(-) create mode 100755 keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java create mode 100755 keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordForm.java create mode 100755 keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordFormFactory.java rename keycloak/sms-provider/src/main/java/org/sunbird/keycloak/{ => resetcredential/sms}/KeycloakSmsAuthenticator.java (99%) rename keycloak/sms-provider/src/main/java/org/sunbird/keycloak/{ => resetcredential/sms}/KeycloakSmsAuthenticatorConstants.java (90%) rename keycloak/sms-provider/src/main/java/org/sunbird/keycloak/{ => resetcredential/sms}/KeycloakSmsAuthenticatorCredentialProvider.java (99%) rename keycloak/sms-provider/src/main/java/org/sunbird/keycloak/{ => resetcredential/sms}/KeycloakSmsAuthenticatorCredentialProviderFactory.java (94%) rename keycloak/sms-provider/src/main/java/org/sunbird/keycloak/{ => resetcredential/sms}/KeycloakSmsAuthenticatorFactory.java (98%) rename keycloak/sms-provider/src/main/java/org/sunbird/keycloak/{ => resetcredential/sms}/KeycloakSmsAuthenticatorUtil.java (99%) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java new file mode 100755 index 00000000..6b1e41ab --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java @@ -0,0 +1,211 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sunbird.keycloak.login.phone; + +import org.jboss.logging.Logger; +import org.keycloak.authentication.AbstractFormAuthenticator; +import org.keycloak.authentication.AuthenticationFlowContext; +import org.keycloak.authentication.AuthenticationFlowError; +import org.keycloak.credential.CredentialInput; +import org.keycloak.events.Details; +import org.keycloak.events.Errors; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.ServicesLogger; +import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.messages.Messages; +import org.sunbird.keycloak.resetcredential.sms.KeycloakSmsAuthenticatorConstants; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public abstract class AbstractPhoneFormAuthenticator extends AbstractFormAuthenticator { + + private static final Logger logger = Logger.getLogger(AbstractPhoneFormAuthenticator.class); + + public static final String REGISTRATION_FORM_ACTION = "registration_form"; + public static final String ATTEMPTED_USERNAME = "ATTEMPTED_USERNAME"; + + @Override + public void action(AuthenticationFlowContext context) { + + } + + protected Response invalidUser(AuthenticationFlowContext context) { + return context.form() + .setError(Messages.INVALID_USER) + .createLogin(); + } + + protected Response disabledUser(AuthenticationFlowContext context) { + return context.form() + .setError(Messages.ACCOUNT_DISABLED).createLogin(); + } + + protected Response temporarilyDisabledUser(AuthenticationFlowContext context) { + return context.form() + .setError(Messages.INVALID_USER).createLogin(); + } + + protected Response invalidCredentials(AuthenticationFlowContext context) { + return context.form() + .setError(Messages.INVALID_USER).createLogin(); + } + + protected Response setDuplicateUserChallenge(AuthenticationFlowContext context, String eventError, String loginFormError, AuthenticationFlowError authenticatorError) { + context.getEvent().error(eventError); + Response challengeResponse = context.form() + .setError(loginFormError).createLogin(); + context.failureChallenge(authenticatorError, challengeResponse); + return challengeResponse; + } + + public boolean invalidUser(AuthenticationFlowContext context, UserModel user) { + if (user == null) { + context.getEvent().error(Errors.USER_NOT_FOUND); + Response challengeResponse = invalidUser(context); + context.failureChallenge(AuthenticationFlowError.INVALID_USER, challengeResponse); + return true; + } + return false; + } + + public boolean enabledUser(AuthenticationFlowContext context, UserModel user) { + if (!user.isEnabled()) { + context.getEvent().user(user); + context.getEvent().error(Errors.USER_DISABLED); + Response challengeResponse = disabledUser(context); + context.failureChallenge(AuthenticationFlowError.USER_DISABLED, challengeResponse); + return false; + } + if (context.getRealm().isBruteForceProtected()) { + if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user)) { + context.getEvent().user(user); + context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED); + Response challengeResponse = temporarilyDisabledUser(context); + context.failureChallenge(AuthenticationFlowError.USER_TEMPORARILY_DISABLED, challengeResponse); + return false; + } + } + return true; + } + + public boolean validateUserAndPassword(AuthenticationFlowContext context, MultivaluedMap inputData) { + String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME); + logger.error("AbstractPhoneFormAuthenticator@validateUserAndPassword - Username -" + username); + + if (username == null) { + context.getEvent().error(Errors.USER_NOT_FOUND); + Response challengeResponse = invalidUser(context); + context.failureChallenge(AuthenticationFlowError.INVALID_USER, challengeResponse); + return false; + } + + // remove leading and trailing whitespace + username = username.trim(); + + context.getEvent().detail(Details.USERNAME, username); + context.getAuthenticationSession().setAuthNote(AbstractPhoneFormAuthenticator.ATTEMPTED_USERNAME, username); + + UserModel user = null; + try { + user = getUser(context, username); + } catch (ModelDuplicateException mde) { + ServicesLogger.LOGGER.modelDuplicateException(mde); + + // Could happen during federation import + if (mde.getDuplicateFieldName() != null && mde.getDuplicateFieldName().equals(UserModel.EMAIL)) { + setDuplicateUserChallenge(context, Errors.EMAIL_IN_USE, Messages.EMAIL_EXISTS, AuthenticationFlowError.INVALID_USER); + } else { + setDuplicateUserChallenge(context, Errors.USERNAME_IN_USE, Messages.USERNAME_EXISTS, AuthenticationFlowError.INVALID_USER); + } + + return false; + } + + if (invalidUser(context, user)) { + return false; + } + + if (!validatePassword(context, user, inputData)) { + return false; + } + + if (!enabledUser(context, user)) { + return false; + } + + String rememberMe = inputData.getFirst("rememberMe"); + boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on"); + if (remember) { + context.getAuthenticationSession().setAuthNote(Details.REMEMBER_ME, "true"); + context.getEvent().detail(Details.REMEMBER_ME, "true"); + } else { + context.getAuthenticationSession().removeAuthNote(Details.REMEMBER_ME); + } + context.setUser(user); + return true; + } + + private UserModel getUser(AuthenticationFlowContext context, String username) { + String numberRegex = "\\d+"; + KeycloakSession session = context.getSession(); + + if (username.matches(numberRegex)) { + List userModels = session.users().searchForUserByUserAttribute(KeycloakSmsAuthenticatorConstants.ATTR_MOBILE, username, context.getRealm()); + + if (userModels != null && userModels.size() > 0) { + logger.error("AbstractPhoneFormAuthenticator@getUser : Users List - " + userModels.size()); + logger.error("AbstractPhoneFormAuthenticator@getUser : First User - " + userModels.get(0).getUsername()); + return userModels.get(0); + } + } else { + return KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username); + } + + logger.error("AbstractPhoneFormAuthenticator@getUser : No user found!"); + return null; + } + + public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap inputData) { + List credentials = new LinkedList<>(); + String password = inputData.getFirst(CredentialRepresentation.PASSWORD); + credentials.add(UserCredentialModel.password(password)); + if (password != null && !password.isEmpty() && context.getSession().userCredentialManager().isValid(context.getRealm(), user, credentials)) { + return true; + } else { + context.getEvent().user(user); + context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); + Response challengeResponse = invalidCredentials(context); + context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse); + context.clearUser(); + return false; + } + } + +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordForm.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordForm.java new file mode 100755 index 00000000..4547a174 --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordForm.java @@ -0,0 +1,112 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sunbird.keycloak.login.phone; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; +import org.keycloak.authentication.AuthenticationFlowContext; +import org.keycloak.authentication.AuthenticationProcessor; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator; +import org.keycloak.forms.login.LoginFormsProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.services.ServicesLogger; +import org.keycloak.services.managers.AuthenticationManager; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class PhonePasswordForm extends AbstractPhoneFormAuthenticator implements Authenticator { + protected static ServicesLogger log = ServicesLogger.LOGGER; + private static final Logger logger = Logger.getLogger(PhonePasswordForm.class); + + @Override + public void action(AuthenticationFlowContext context) { + logger.error("PhonePasswordForm@action - called"); + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + if (formData.containsKey("cancel")) { + context.cancelLogin(); + return; + } + if (!validateForm(context, formData)) { + return; + } + context.success(); + } + + protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap formData) { + logger.error("PhonePasswordForm@validateForm - called"); + return validateUserAndPassword(context, formData); + } + + @Override + public void authenticate(AuthenticationFlowContext context) { + MultivaluedMap formData = new MultivaluedMapImpl<>(); + String loginHint = context.getAuthenticationSession().getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM); + + String rememberMeUsername = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders()); + + if (loginHint != null || rememberMeUsername != null) { + if (loginHint != null) { + formData.add(AuthenticationManager.FORM_USERNAME, loginHint); + } else { + formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername); + formData.add("rememberMe", "on"); + } + } + Response challengeResponse = challenge(context, formData); + context.challenge(challengeResponse); + } + + @Override + public boolean requiresUser() { + return false; + } + + protected Response challenge(AuthenticationFlowContext context, MultivaluedMap formData) { + LoginFormsProvider forms = context.form(); + + if (formData.size() > 0) forms.setFormData(formData); + + return forms.createLogin(); + } + + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + // never called + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + // never called + } + + @Override + public void close() { + + } +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordFormFactory.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordFormFactory.java new file mode 100755 index 00000000..d3df0db5 --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordFormFactory.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sunbird.keycloak.login.phone; + +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorFactory; +import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.provider.ProviderConfigProperty; + +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class PhonePasswordFormFactory implements AuthenticatorFactory { + + private static final Logger logger = Logger.getLogger(PhonePasswordFormFactory.class); + + public static final String PROVIDER_ID = "auth-phone-password-form"; + public static final PhonePasswordForm SINGLETON = new PhonePasswordForm(); + + @Override + public Authenticator create(KeycloakSession session) { + logger.error("PhonePasswordFormFactory@create - PhonePasswordFormFactory is created"); + return SINGLETON; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getReferenceCategory() { + return UserCredentialModel.PASSWORD; + } + + @Override + public boolean isConfigurable() { + return false; + } + public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { + AuthenticationExecutionModel.Requirement.REQUIRED + }; + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return REQUIREMENT_CHOICES; + } + + @Override + public String getDisplayType() { + return "Phone Password Form"; + } + + @Override + public String getHelpText() { + return "Validates a phone and password from login form."; + } + + @Override + public List getConfigProperties() { + return null; + } + + @Override + public boolean isUserSetupAllowed() { + return false; + } + +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java similarity index 99% rename from keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticator.java rename to keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index 947bf6de..cabfea70 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -1,4 +1,4 @@ -package org.sunbird.keycloak; +package org.sunbird.keycloak.resetcredential.sms; import org.apache.http.util.TextUtils; import org.jboss.logging.Logger; diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorConstants.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorConstants.java similarity index 90% rename from keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorConstants.java rename to keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorConstants.java index a88cefee..ff6e7cf8 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorConstants.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorConstants.java @@ -1,11 +1,11 @@ -package org.sunbird.keycloak; +package org.sunbird.keycloak.resetcredential.sms; /** * Created by joris on 18/11/2016. */ // TODO(shriharshs): Move strings for mobile, smsCode, countryCode, defaultCountry code to configuration public class KeycloakSmsAuthenticatorConstants { - public static final String ATTR_MOBILE = "mobile"; + public static final String ATTR_MOBILE = "phone"; public static final String ANSW_SMS_CODE = "smsCode"; public static final String COUNTRY_CODE = "+91"; public static final String DEFAULT_COUNTRY_CODE = "0"; diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorCredentialProvider.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorCredentialProvider.java similarity index 99% rename from keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorCredentialProvider.java rename to keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorCredentialProvider.java index a0a15ec7..a414ddba 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorCredentialProvider.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorCredentialProvider.java @@ -1,4 +1,4 @@ -package org.sunbird.keycloak; +package org.sunbird.keycloak.resetcredential.sms; import org.jboss.logging.Logger; import org.keycloak.common.util.Time; diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorCredentialProviderFactory.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorCredentialProviderFactory.java similarity index 94% rename from keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorCredentialProviderFactory.java rename to keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorCredentialProviderFactory.java index 6288b0e7..67cc5d59 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorCredentialProviderFactory.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorCredentialProviderFactory.java @@ -1,4 +1,4 @@ -package org.sunbird.keycloak; +package org.sunbird.keycloak.resetcredential.sms; import org.jboss.logging.Logger; import org.keycloak.credential.CredentialProvider; diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorFactory.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorFactory.java similarity index 98% rename from keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorFactory.java rename to keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorFactory.java index 34d935fa..6868ae90 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorFactory.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorFactory.java @@ -1,4 +1,4 @@ -package org.sunbird.keycloak; +package org.sunbird.keycloak.resetcredential.sms; import org.jboss.logging.Logger; import org.keycloak.Config; diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorUtil.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorUtil.java similarity index 99% rename from keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorUtil.java rename to keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorUtil.java index 2480cc37..0db6c74f 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorUtil.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticatorUtil.java @@ -1,4 +1,4 @@ -package org.sunbird.keycloak; +package org.sunbird.keycloak.resetcredential.sms; import com.amazonaws.util.StringUtils; import org.jboss.logging.Logger; diff --git a/keycloak/sms-provider/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/keycloak/sms-provider/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory index 500f32fa..f820d6e0 100755 --- a/keycloak/sms-provider/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory +++ b/keycloak/sms-provider/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory @@ -1 +1,2 @@ -org.sunbird.keycloak.KeycloakSmsAuthenticatorFactory +org.sunbird.keycloak.resetcredential.sms.KeycloakSmsAuthenticatorFactory +org.sunbird.keycloak.login.phone.PhonePasswordFormFactory diff --git a/keycloak/sms-provider/src/main/resources/META-INF/services/org.keycloak.credential.CredentialProviderFactory b/keycloak/sms-provider/src/main/resources/META-INF/services/org.keycloak.credential.CredentialProviderFactory index edd6c136..fd4daa97 100644 --- a/keycloak/sms-provider/src/main/resources/META-INF/services/org.keycloak.credential.CredentialProviderFactory +++ b/keycloak/sms-provider/src/main/resources/META-INF/services/org.keycloak.credential.CredentialProviderFactory @@ -1 +1 @@ -org.sunbird.keycloak.KeycloakSmsAuthenticatorCredentialProviderFactory +org.sunbird.keycloak.resetcredential.sms.KeycloakSmsAuthenticatorCredentialProviderFactory From ea0b08f966ae02d4d2044c227603ff908868b895 Mon Sep 17 00:00:00 2001 From: shriharsh Date: Tue, 19 Dec 2017 11:22:57 +0530 Subject: [PATCH 2/4] Issue #9 chores: fall-back mechanism for when user details does not match with phone number, but contains all characters as digits. --- .../keycloak/login/phone/AbstractPhoneFormAuthenticator.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java index 6b1e41ab..5e91e2c4 100755 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java @@ -183,13 +183,12 @@ private UserModel getUser(AuthenticationFlowContext context, String username) { logger.error("AbstractPhoneFormAuthenticator@getUser : Users List - " + userModels.size()); logger.error("AbstractPhoneFormAuthenticator@getUser : First User - " + userModels.get(0).getUsername()); return userModels.get(0); + } else { + return KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username); } } else { return KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username); } - - logger.error("AbstractPhoneFormAuthenticator@getUser : No user found!"); - return null; } public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap inputData) { From 1801a117992094b5e4d50a32bc5f3864ca7ba187 Mon Sep 17 00:00:00 2001 From: shriharsh Date: Mon, 25 Dec 2017 10:35:53 +0530 Subject: [PATCH 3/4] Issue #14 feature: AbstractUsernameFormAuthenticator is extended in the phone form authenticator now. --- .../phone/AbstractPhoneFormAuthenticator.java | 95 +------------------ 1 file changed, 2 insertions(+), 93 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java index 5e91e2c4..56739e68 100755 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java @@ -18,18 +18,15 @@ package org.sunbird.keycloak.login.phone; import org.jboss.logging.Logger; -import org.keycloak.authentication.AbstractFormAuthenticator; import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationFlowError; -import org.keycloak.credential.CredentialInput; +import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.ServicesLogger; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.messages.Messages; @@ -37,84 +34,12 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -import java.util.LinkedList; import java.util.List; -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public abstract class AbstractPhoneFormAuthenticator extends AbstractFormAuthenticator { +public abstract class AbstractPhoneFormAuthenticator extends AbstractUsernameFormAuthenticator { private static final Logger logger = Logger.getLogger(AbstractPhoneFormAuthenticator.class); - public static final String REGISTRATION_FORM_ACTION = "registration_form"; - public static final String ATTEMPTED_USERNAME = "ATTEMPTED_USERNAME"; - - @Override - public void action(AuthenticationFlowContext context) { - - } - - protected Response invalidUser(AuthenticationFlowContext context) { - return context.form() - .setError(Messages.INVALID_USER) - .createLogin(); - } - - protected Response disabledUser(AuthenticationFlowContext context) { - return context.form() - .setError(Messages.ACCOUNT_DISABLED).createLogin(); - } - - protected Response temporarilyDisabledUser(AuthenticationFlowContext context) { - return context.form() - .setError(Messages.INVALID_USER).createLogin(); - } - - protected Response invalidCredentials(AuthenticationFlowContext context) { - return context.form() - .setError(Messages.INVALID_USER).createLogin(); - } - - protected Response setDuplicateUserChallenge(AuthenticationFlowContext context, String eventError, String loginFormError, AuthenticationFlowError authenticatorError) { - context.getEvent().error(eventError); - Response challengeResponse = context.form() - .setError(loginFormError).createLogin(); - context.failureChallenge(authenticatorError, challengeResponse); - return challengeResponse; - } - - public boolean invalidUser(AuthenticationFlowContext context, UserModel user) { - if (user == null) { - context.getEvent().error(Errors.USER_NOT_FOUND); - Response challengeResponse = invalidUser(context); - context.failureChallenge(AuthenticationFlowError.INVALID_USER, challengeResponse); - return true; - } - return false; - } - - public boolean enabledUser(AuthenticationFlowContext context, UserModel user) { - if (!user.isEnabled()) { - context.getEvent().user(user); - context.getEvent().error(Errors.USER_DISABLED); - Response challengeResponse = disabledUser(context); - context.failureChallenge(AuthenticationFlowError.USER_DISABLED, challengeResponse); - return false; - } - if (context.getRealm().isBruteForceProtected()) { - if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user)) { - context.getEvent().user(user); - context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED); - Response challengeResponse = temporarilyDisabledUser(context); - context.failureChallenge(AuthenticationFlowError.USER_TEMPORARILY_DISABLED, challengeResponse); - return false; - } - } - return true; - } - public boolean validateUserAndPassword(AuthenticationFlowContext context, MultivaluedMap inputData) { String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME); logger.error("AbstractPhoneFormAuthenticator@validateUserAndPassword - Username -" + username); @@ -191,20 +116,4 @@ private UserModel getUser(AuthenticationFlowContext context, String username) { } } - public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap inputData) { - List credentials = new LinkedList<>(); - String password = inputData.getFirst(CredentialRepresentation.PASSWORD); - credentials.add(UserCredentialModel.password(password)); - if (password != null && !password.isEmpty() && context.getSession().userCredentialManager().isValid(context.getRealm(), user, credentials)) { - return true; - } else { - context.getEvent().user(user); - context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); - Response challengeResponse = invalidCredentials(context); - context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse); - context.clearUser(); - return false; - } - } - } From deced7856cff9d8a25cc2a02207f50666f0b8ef0 Mon Sep 17 00:00:00 2001 From: shriharsh Date: Wed, 27 Dec 2017 09:43:48 +0530 Subject: [PATCH 4/4] Issue #14 Chore: Logger type changed from error to debug --- .../keycloak/login/phone/AbstractPhoneFormAuthenticator.java | 4 +--- .../org/sunbird/keycloak/login/phone/PhonePasswordForm.java | 4 ++-- .../keycloak/login/phone/PhonePasswordFormFactory.java | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java index 56739e68..8f8c2051 100755 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/AbstractPhoneFormAuthenticator.java @@ -42,7 +42,7 @@ public abstract class AbstractPhoneFormAuthenticator extends AbstractUsernameFor public boolean validateUserAndPassword(AuthenticationFlowContext context, MultivaluedMap inputData) { String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME); - logger.error("AbstractPhoneFormAuthenticator@validateUserAndPassword - Username -" + username); + logger.debug("AbstractPhoneFormAuthenticator@validateUserAndPassword - Username -" + username); if (username == null) { context.getEvent().error(Errors.USER_NOT_FOUND); @@ -105,8 +105,6 @@ private UserModel getUser(AuthenticationFlowContext context, String username) { List userModels = session.users().searchForUserByUserAttribute(KeycloakSmsAuthenticatorConstants.ATTR_MOBILE, username, context.getRealm()); if (userModels != null && userModels.size() > 0) { - logger.error("AbstractPhoneFormAuthenticator@getUser : Users List - " + userModels.size()); - logger.error("AbstractPhoneFormAuthenticator@getUser : First User - " + userModels.get(0).getUsername()); return userModels.get(0); } else { return KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username); diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordForm.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordForm.java index 4547a174..2179e80d 100755 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordForm.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordForm.java @@ -44,7 +44,7 @@ public class PhonePasswordForm extends AbstractPhoneFormAuthenticator implements @Override public void action(AuthenticationFlowContext context) { - logger.error("PhonePasswordForm@action - called"); + logger.debug("PhonePasswordForm@action - called"); MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); if (formData.containsKey("cancel")) { context.cancelLogin(); @@ -57,7 +57,7 @@ public void action(AuthenticationFlowContext context) { } protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap formData) { - logger.error("PhonePasswordForm@validateForm - called"); + logger.debug("PhonePasswordForm@validateForm - called"); return validateUserAndPassword(context, formData); } diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordFormFactory.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordFormFactory.java index d3df0db5..7eaed729 100755 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordFormFactory.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/phone/PhonePasswordFormFactory.java @@ -43,7 +43,7 @@ public class PhonePasswordFormFactory implements AuthenticatorFactory { @Override public Authenticator create(KeycloakSession session) { - logger.error("PhonePasswordFormFactory@create - PhonePasswordFormFactory is created"); + logger.debug("PhonePasswordFormFactory@create - PhonePasswordFormFactory is created"); return SINGLETON; }