-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[BACK-3225] Add authenticator that looks up SMART IDPs given issuer q…
…uery param
- Loading branch information
1 parent
a3b2998
commit 623b332
Showing
3 changed files
with
173 additions
and
0 deletions.
There are no files selected for viewing
93 changes: 93 additions & 0 deletions
93
...va/org/tidepool/keycloak/extensions/authenticator/SMARTIdentityProviderAuthenticator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package org.tidepool.keycloak.extensions.authenticator; | ||
|
||
import org.jboss.logging.Logger; | ||
import org.keycloak.authentication.AuthenticationFlowContext; | ||
import org.keycloak.authentication.AuthenticationProcessor; | ||
import org.keycloak.authentication.Authenticator; | ||
import org.keycloak.models.IdentityProviderModel; | ||
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.Urls; | ||
import org.keycloak.services.managers.ClientSessionCode; | ||
|
||
import jakarta.ws.rs.core.Response; | ||
import java.net.URI; | ||
import java.util.Optional; | ||
|
||
public class SMARTIdentityProviderAuthenticator implements Authenticator { | ||
|
||
private static final Logger LOG = Logger.getLogger(SMARTIdentityProviderAuthenticator.class); | ||
|
||
protected static final String ACCEPTS_PROMPT_NONE = "acceptsPromptNoneForwardFromClient"; | ||
|
||
private static final String ISSUER = "iss"; | ||
|
||
@Override | ||
public void authenticate(AuthenticationFlowContext context) { | ||
String issuer = context.getUriInfo().getQueryParameters().getFirst(ISSUER); | ||
if (issuer == null || issuer.isBlank()) { | ||
LOG.warnf("No issuer set or %s query parameter provided", ISSUER); | ||
context.attempted(); | ||
return; | ||
} | ||
|
||
LOG.infof("Redirecting: %s set to %s", ISSUER, issuer); | ||
redirect(context, issuer); | ||
} | ||
|
||
protected void redirect(AuthenticationFlowContext context, String issuer) { | ||
Optional<IdentityProviderModel> idp = context.getRealm().getIdentityProvidersStream() | ||
.filter(IdentityProviderModel::isEnabled) | ||
.filter(identityProvider -> identityProvider.getConfig().getOrDefault("issuer", "").equals(issuer)) | ||
.findFirst(); | ||
if (idp.isPresent()) { | ||
String providerId = idp.get().getProviderId(); | ||
|
||
String accessCode = new ClientSessionCode<>(context.getSession(), context.getRealm(), context.getAuthenticationSession()).getOrGenerateCode(); | ||
String clientId = context.getAuthenticationSession().getClient().getClientId(); | ||
String tabId = context.getAuthenticationSession().getTabId(); | ||
String clientData = AuthenticationProcessor.getClientData(context.getSession(), context.getAuthenticationSession()); | ||
URI location = Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode, clientId, tabId, clientData, null); | ||
Response response = Response.seeOther(location) | ||
.build(); | ||
|
||
// will forward the request to the IDP with prompt=none if the IDP accepts forwards with prompt=none. | ||
if ("none".equals(context.getAuthenticationSession().getClientNote(OIDCLoginProtocol.PROMPT_PARAM)) && | ||
Boolean.parseBoolean(idp.get().getConfig().get(ACCEPTS_PROMPT_NONE))) { | ||
context.getAuthenticationSession().setAuthNote(AuthenticationProcessor.FORWARDED_PASSIVE_LOGIN, "true"); | ||
} | ||
|
||
LOG.debugf("Redirecting to %s", providerId); | ||
context.forceChallenge(response); | ||
return; | ||
} | ||
|
||
LOG.warnf("Smart issuer %s not found or not enabled for realm", issuer); | ||
context.attempted(); | ||
} | ||
|
||
@Override | ||
public void action(AuthenticationFlowContext context) { | ||
} | ||
|
||
@Override | ||
public boolean requiresUser() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { | ||
return true; | ||
} | ||
|
||
@Override | ||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { | ||
} | ||
|
||
@Override | ||
public void close() { | ||
} | ||
|
||
} |
79 changes: 79 additions & 0 deletions
79
...tidepool/keycloak/extensions/authenticator/SMARTIdentityProviderAuthenticatorFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package org.tidepool.keycloak.extensions.authenticator; | ||
|
||
import org.keycloak.Config; | ||
import org.keycloak.authentication.Authenticator; | ||
import org.keycloak.authentication.AuthenticatorFactory; | ||
import org.keycloak.models.AuthenticationExecutionModel; | ||
import org.keycloak.models.KeycloakSession; | ||
import org.keycloak.models.KeycloakSessionFactory; | ||
import org.keycloak.provider.ProviderConfigProperty; | ||
|
||
import java.util.List; | ||
|
||
public class SMARTIdentityProviderAuthenticatorFactory implements AuthenticatorFactory { | ||
protected static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { | ||
AuthenticationExecutionModel.Requirement.REQUIRED, | ||
AuthenticationExecutionModel.Requirement.ALTERNATIVE, | ||
AuthenticationExecutionModel.Requirement.DISABLED | ||
}; | ||
|
||
public static final String PROVIDER_ID = "smart-identity-provider-redirector"; | ||
|
||
@Override | ||
public String getDisplayType() { | ||
return "SMART Identity Provider Redirector"; | ||
} | ||
|
||
@Override | ||
public String getReferenceCategory() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public boolean isConfigurable() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { | ||
return REQUIREMENT_CHOICES; | ||
} | ||
|
||
@Override | ||
public boolean isUserSetupAllowed() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public String getHelpText() { | ||
return "Redirects to a SMART Identity Provider specified with issuer query parameter"; | ||
} | ||
|
||
@Override | ||
public List<ProviderConfigProperty> getConfigProperties() { | ||
return List.of(); | ||
} | ||
|
||
@Override | ||
public Authenticator create(KeycloakSession session) { | ||
return new SMARTIdentityProviderAuthenticator(); | ||
} | ||
|
||
@Override | ||
public void init(Config.Scope config) { | ||
} | ||
|
||
@Override | ||
public void postInit(KeycloakSessionFactory factory) { | ||
} | ||
|
||
@Override | ||
public void close() { | ||
} | ||
|
||
@Override | ||
public String getId() { | ||
return PROVIDER_ID; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters