Skip to content

Commit

Permalink
[BACK-3225] Add authenticator that looks up SMART IDPs given issuer q…
Browse files Browse the repository at this point in the history
…uery param
  • Loading branch information
toddkazakov committed Oct 24, 2024
1 parent a3b2998 commit 623b332
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 0 deletions.
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() {
}

}
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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ org.tidepool.keycloak.extensions.authenticator.ConditionUserInContextFactory
org.tidepool.keycloak.extensions.authenticator.RedirectToRegistrationPageFactory
org.tidepool.keycloak.extensions.authenticator.RegistrationRoleDiscoveryAuthenticatorFactory
org.tidepool.keycloak.extensions.authenticator.ResetUserInContextFactory
org.tidepool.keycloak.extensions.authenticator.SMARTIdentityProviderAuthenticatorFactory

0 comments on commit 623b332

Please sign in to comment.