Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify the Magic Link for API Based Authentication #40

Merged
merged 2 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,10 @@
<rule implementation="org.jacoco.maven.RuleConfiguration">
<element>BUNDLE</element>
<limits>
<limit implementation="org.jacoco.report.check.Limit">
<limit implementation="org.jacoco.report.check.Limit">
<counter>COMPLEXITY</counter>
<value>COVEREDRATIO</value>
<minimum>0.30</minimum>
<minimum> 0.17</minimum>
</limit>
</limits>
</rule>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
import org.wso2.carbon.identity.application.authentication.framework.exception.InvalidCredentialsException;
import org.wso2.carbon.identity.application.authentication.framework.exception.LogoutFailedException;
import org.wso2.carbon.identity.application.authentication.framework.exception.UserIdNotFoundException;
import org.wso2.carbon.identity.application.authentication.framework.model.AdditionalData;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatorData;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatorParamMetadata;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.authenticator.magiclink.cache.MagicLinkAuthContextCache;
Expand Down Expand Up @@ -72,6 +75,10 @@
import static org.wso2.carbon.identity.application.authenticator.magiclink.MagicLinkAuthenticatorConstants.LogConstants.ActionIDs.SEND_MAGIC_LINK;
import static org.wso2.carbon.identity.application.authenticator.magiclink.MagicLinkAuthenticatorConstants.LogConstants.ActionIDs.VALIDATE_MAGIC_LINK_REQUEST;
import static org.wso2.carbon.identity.application.authenticator.magiclink.MagicLinkAuthenticatorConstants.LogConstants.MAGIC_LINK_AUTH_SERVICE;
import static org.wso2.carbon.identity.application.authenticator.magiclink.MagicLinkAuthenticatorConstants.USERNAME_PARAM;
import static org.wso2.carbon.identity.application.authenticator.magiclink.MagicLinkAuthenticatorConstants.USER_NAME;
import static org.wso2.carbon.identity.application.authenticator.magiclink.MagicLinkAuthenticatorConstants.USER_PROMPT;
import static org.wso2.carbon.identity.application.authenticator.magiclink.MagicLinkAuthenticatorConstants.MLT;

/**
* Authenticator of MagicLink.
Expand All @@ -80,6 +87,7 @@ public class MagicLinkAuthenticator extends AbstractApplicationAuthenticator imp

private static final long serialVersionUID = 4345354156955223654L;
private static final Log log = LogFactory.getLog(MagicLinkAuthenticator.class);
public static final String REDIRECT_URL = "REDIRECT_URL";
private AuthenticationContext authenticationContext;

/**
Expand Down Expand Up @@ -116,6 +124,11 @@ public AuthenticatorFlowStatus process(HttpServletRequest request, HttpServletRe
return AuthenticatorFlowStatus.INCOMPLETE;
}

private boolean isAPIBasedAuthenticationFlow(HttpServletRequest request) {

return Boolean.TRUE.equals(request.getAttribute(FrameworkConstants.IS_API_BASED_AUTH_FLOW));
}

/**
* This method is used initiate authenticate request.
*
Expand Down Expand Up @@ -169,7 +182,7 @@ protected void initiateAuthenticationRequest(HttpServletRequest request, HttpSer
} catch (IOException e) {
org.wso2.carbon.identity.application.common.model.User user =
org.wso2.carbon.identity.application.common.model.User
.getUserFromUserName(request.getParameter(MagicLinkAuthenticatorConstants.USER_NAME));
.getUserFromUserName(request.getParameter(USER_NAME));
throw new AuthenticationFailedException(
MagicLinkAuthErrorConstants.ErrorMessages.SYSTEM_ERROR_WHILE_AUTHENTICATING.getCode(),
e.getMessage(), user, e);
Expand Down Expand Up @@ -303,7 +316,7 @@ public boolean canHandle(HttpServletRequest httpServletRequest) {
if (log.isDebugEnabled()) {
log.debug("Magic link authenticator is handling identifier first flow ");
}
String userName = httpServletRequest.getParameter(MagicLinkAuthenticatorConstants.USER_NAME);
String userName = httpServletRequest.getParameter(USER_NAME);
String restart = httpServletRequest.getParameter(RESTART_FLOW);
boolean canHandle = StringUtils.isNotEmpty(userName) || StringUtils.isNotEmpty(restart);
if (LoggerUtils.isDiagnosticLogsEnabled() && diagnosticLogBuilder != null && canHandle) {
Expand Down Expand Up @@ -366,6 +379,8 @@ protected void triggerEvent(User user, AuthenticationContext context, String mag
properties.put(MagicLinkAuthenticatorConstants.TEMPLATE_TYPE, MagicLinkAuthenticatorConstants.EVENT_NAME);
properties.put(IdentityEventConstants.EventProperty.APPLICATION_NAME, context.getServiceProviderName());
properties.put(MagicLinkAuthenticatorConstants.EXPIRYTIME, expiryTime);
properties.put(MagicLinkAuthenticatorConstants.IS_API_BASED_AUTHENTICATION_SUPPORTED, "true");
properties.put(MagicLinkAuthenticatorConstants.CALLBACK_URL, context.getProperty(REDIRECT_URL));
Event identityMgtEvent = new Event(eventName, properties);
DiagnosticLog.DiagnosticLogBuilder diagnosticLogBuilder = null;
if (LoggerUtils.isDiagnosticLogsEnabled()) {
Expand Down Expand Up @@ -397,6 +412,16 @@ protected void triggerEvent(User user, AuthenticationContext context, String mag
}
}

private void setAuthParams(AuthenticatorData authenticatorData) {

List<AuthenticatorParamMetadata> authenticatorParamMetadataList = new ArrayList<>();
AuthenticatorParamMetadata usernameMetadata = new AuthenticatorParamMetadata(
MagicLinkAuthenticatorConstants.USER_NAME, FrameworkConstants.AuthenticatorParamType.STRING,
0, Boolean.TRUE, MagicLinkAuthenticatorConstants.MAGIC_LINK_CODE);
authenticatorParamMetadataList.add(usernameMetadata);
authenticatorData.setAuthParams(authenticatorParamMetadataList);
}

private boolean isMagicTokenValid(MagicLinkAuthContextCacheEntry cacheEntry) {

if (cacheEntry != null) {
Expand Down Expand Up @@ -452,7 +477,7 @@ private List<String> getBlockedUserStoreDomainsList() {
private String validateIdentifierFromRequest(HttpServletRequest request)
throws AuthenticationFailedException {

String identifierFromRequest = request.getParameter(MagicLinkAuthenticatorConstants.USER_NAME);
String identifierFromRequest = request.getParameter(USER_NAME);
if (StringUtils.isBlank(identifierFromRequest)) {
throw new InvalidCredentialsException(MagicLinkAuthErrorConstants.ErrorMessages.EMPTY_USERNAME.getCode(),
MagicLinkAuthErrorConstants.ErrorMessages.EMPTY_USERNAME.getMessage());
Expand Down Expand Up @@ -494,7 +519,7 @@ private void setResolvedUserInContext(AuthenticationContext context, User user)

String username = UserCoreUtil.addTenantDomainToEntry(user.getUsername(), user.getTenantDomain());
username = FrameworkUtils.prependUserStoreDomainToName(username);
authProperties.put(MagicLinkAuthenticatorConstants.USER_NAME, username);
authProperties.put(USER_NAME, username);
addUsernameToContext(context, username);
setSubjectInContext(context, user);
}
Expand Down Expand Up @@ -563,4 +588,72 @@ private Optional<String> getUserId(AuthenticationContext context) {
}
});
}

/**
* This method is responsible for validating whether the authenticator is supported for API Based Authentication.
*
* @return true if the authenticator is supported for API Based Authentication.
*/
@Override
public boolean isAPIBasedAuthenticationSupported() {

return true;
}

/**
* This method is responsible for obtaining authenticator-specific data needed to
* initialize the authentication process within the provided authentication context.
*
* @param context The authentication context containing information about the current authentication attempt.
* @return An {@code Optional} containing an {@code AuthenticatorData} object representing the initiation data.
* If the initiation data is available, it is encapsulated within the {@code Optional}; otherwise,
* an empty {@code Optional} is returned.
*/
/**
* This method is responsible for obtaining authenticator-specific data needed to
* initialize the authentication process within the provided authentication context.
*
* @param context The authentication context containing information about the current authentication attempt.
* @return An {@code Optional} containing an {@code AuthenticatorData} object representing the initiation data.
* If the initiation data is available, it is encapsulated within the {@code Optional}; otherwise,
* an empty {@code Optional} is returned.
*/
@Override
public Optional<AuthenticatorData> getAuthInitiationData(AuthenticationContext context) {

String idpName = null;
if (context != null && context.getExternalIdP() != null) {
idpName = context.getExternalIdP().getIdPName();
}

AuthenticatorData authenticatorData = new AuthenticatorData();
authenticatorData.setName(getName());
authenticatorData.setIdp(idpName);
authenticatorData.setDisplayName(getFriendlyName());
authenticatorData.setPromptType(FrameworkConstants.AuthenticatorPromptType.USER_PROMPT);

if (isIdfInitiatedFromMagicLink()) {
List<String> requiredParams = new ArrayList<>();
requiredParams.add(USER_NAME);
authenticatorData.setRequiredParams(requiredParams);
setAuthParamsForIdfInitiatedFromMagicLink(authenticatorData);
} else {
List<String> requiredParams = new ArrayList<>();
requiredParams.add(MLT);
authenticatorData.setRequiredParams(requiredParams);
setAuthParams(authenticatorData);
}

return Optional.of(authenticatorData);
}

private void setAuthParamsForIdfInitiatedFromMagicLink(AuthenticatorData authenticatorData) {

List<AuthenticatorParamMetadata> authenticatorParamMetadataList = new ArrayList<>();
AuthenticatorParamMetadata usernameMetadata = new AuthenticatorParamMetadata(
USER_NAME, FrameworkConstants.AuthenticatorParamType.STRING,
0, Boolean.FALSE, USERNAME_PARAM);
authenticatorParamMetadataList.add(usernameMetadata);
authenticatorData.setAuthParams(authenticatorParamMetadataList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ private MagicLinkAuthenticatorConstants() {
}

public static final String AUTHENTICATOR_NAME = "MagicLinkAuthenticator";
public static final String MLT = "mlt";
public static final String AUTHENTICATOR_FRIENDLY_NAME = "Magic Link";
public static final String MAGIC_LINK_NOTIFICATION_PAGE = "authenticationendpoint/magic_link_notification.do";
public static final String MAGIC_LINK_TOKEN = "mlt";
Expand All @@ -40,6 +41,9 @@ private MagicLinkAuthenticatorConstants() {
public static final String IDF_HANDLER_NAME = "IdentifierExecutor";
public static final String LOCAL = "LOCAL";
public static final String USER_NAME = "username";
public static final String USERNAME_PARAM = "username.param";
public static final String MAGIC_LINK_CODE = "magic.link.code.param";
public static final String USER_PROMPT = "USER_PROMPT";

// Default expiry time in seconds.
public static final long DEFAULT_EXPIRY_TIME = 300;
Expand All @@ -48,6 +52,8 @@ private MagicLinkAuthenticatorConstants() {
public static final String TEMPLATE_TYPE = "TEMPLATE_TYPE";
public static final String EVENT_NAME = "magicLink";
public static final String EXPIRYTIME = "expiry-time";
public static final String IS_API_BASED_AUTHENTICATION_SUPPORTED = "isAPIBasedAuthenticationSupported";
public static final String CALLBACK_URL = "callbackUrl";

/**
* Constants related to log management.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,16 @@
import org.testng.annotations.Test;
import org.wso2.carbon.identity.application.authentication.framework.AuthenticatorFlowStatus;
import org.wso2.carbon.identity.application.authentication.framework.config.ConfigurationFacade;
import org.wso2.carbon.identity.application.authentication.framework.config.model.ExternalIdPConfig;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException;
import org.wso2.carbon.identity.application.authentication.framework.exception.InvalidCredentialsException;
import org.wso2.carbon.identity.application.authentication.framework.internal.FrameworkServiceDataHolder;
import org.wso2.carbon.identity.application.authentication.framework.model.AdditionalData;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatorData;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatorParamMetadata;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.authenticator.magiclink.cache.MagicLinkAuthContextCache;
import org.wso2.carbon.identity.application.authenticator.magiclink.cache.MagicLinkAuthContextCacheEntry;
Expand Down Expand Up @@ -80,6 +85,8 @@
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertThrows;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.USERNAME_CLAIM;
import static org.wso2.carbon.identity.application.authenticator.magiclink.MagicLinkAuthenticatorConstants.USERNAME_PARAM;
import static org.wso2.carbon.identity.application.authenticator.magiclink.MagicLinkAuthenticatorConstants.USER_NAME;

@PrepareForTest({ TokenGenerator.class, IdentityUtil.class, ServiceURLBuilder.class, IdentityTenantUtil.class,
AbstractUserStoreManager.class, MagicLinkAuthContextCache.class, MagicLinkServiceDataHolder.class,
Expand Down Expand Up @@ -129,6 +136,9 @@ public class MagicLinkAuthenticatorTest {
@Mock
private ConfigurationFacade mockConfigurationFacade;

@Mock
ExternalIdPConfig externalIdPConfig;

private FrameworkServiceDataHolder frameworkServiceDataHolder;

@BeforeMethod
Expand Down Expand Up @@ -544,4 +554,48 @@ public IObjectFactory getObjectFactory() {

return new PowerMockObjectFactory();
}

@Test
public void testIsAPIBasedAuthenticationSupported() {

boolean isAPIBasedAuthenticationSupported = magicLinkAuthenticator.isAPIBasedAuthenticationSupported();
Assert.assertTrue(isAPIBasedAuthenticationSupported);
}

@Test
public void testGetAuthInitiationData() {

when(context.getExternalIdP()).thenReturn(externalIdPConfig);
when(context.getExternalIdP().getIdPName()).thenReturn(MagicLinkAuthenticatorConstants.LOCAL);
when(context.getProperty(MagicLinkAuthenticatorConstants.IS_IDF_INITIATED_FROM_AUTHENTICATOR))
.thenReturn(Boolean.TRUE);

Optional<AuthenticatorData> authenticatorData = magicLinkAuthenticator.getAuthInitiationData(context);
Assert.assertTrue(authenticatorData.isPresent());
AuthenticatorData authenticatorDataObj = authenticatorData.get();

List<AuthenticatorParamMetadata> authenticatorParamMetadataList = new ArrayList<>();
AuthenticatorParamMetadata usernameMetadata = new AuthenticatorParamMetadata(
MagicLinkAuthenticatorConstants.USER_NAME, FrameworkConstants.AuthenticatorParamType.STRING,
0, Boolean.FALSE, MagicLinkAuthenticatorConstants.USERNAME_PARAM);
authenticatorParamMetadataList.add(usernameMetadata);


Assert.assertEquals(authenticatorDataObj.getName(), MagicLinkAuthenticatorConstants.AUTHENTICATOR_NAME);
Assert.assertEquals(authenticatorDataObj.getAuthParams().size(), authenticatorParamMetadataList.size(),
"Size of lists should be equal.");
Assert.assertEquals(authenticatorDataObj.getPromptType(),
MagicLinkAuthenticatorConstants.USER_PROMPT, "Prompt Type should match.");
for (int i = 0; i < authenticatorParamMetadataList.size(); i++) {
AuthenticatorParamMetadata expectedParam = authenticatorParamMetadataList.get(i);
AuthenticatorParamMetadata actualParam = authenticatorDataObj.getAuthParams().get(i);

Assert.assertEquals(actualParam.getName(), expectedParam.getName(), "Parameter name should match.");
Assert.assertEquals(actualParam.getType(), expectedParam.getType(), "Parameter type should match.");
Assert.assertEquals(actualParam.getParamOrder(), expectedParam.getParamOrder(),
"Parameter order should match.");
Assert.assertEquals(actualParam.isConfidential(), expectedParam.isConfidential(),
"Parameter confidential status should match.");
}
}
}
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,10 @@
<identity.application.authenticator.magiclink.exp.pkg.version>${project.version}
</identity.application.authenticator.magiclink.exp.pkg.version>
<carbon.kernel.version>4.9.10</carbon.kernel.version>
<carbon.identity.framework.version>5.25.260</carbon.identity.framework.version>
<carbon.identity.framework.version>5.25.460</carbon.identity.framework.version>
<apache.felix.scr.ds.annotations.version>1.2.10</apache.felix.scr.ds.annotations.version>
<identity.event.handler.notification.version>1.3.14</identity.event.handler.notification.version>
<identity.inbound.auth.oauth.version>6.4.126</identity.inbound.auth.oauth.version>
<identity.inbound.auth.oauth.version>6.11.172</identity.inbound.auth.oauth.version>
<identity.outbound.auth.oidc.version>5.1.2</identity.outbound.auth.oidc.version>
<commons-logging.version>4.4.3</commons-logging.version>
<carbon.p2.plugin.version>1.5.3</carbon.p2.plugin.version>
Expand Down
Loading