Skip to content

Commit

Permalink
[BACK-3125] Add SMART identity provider
Browse files Browse the repository at this point in the history
  • Loading branch information
toddkazakov committed Oct 24, 2024
1 parent 78de033 commit a3b2998
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.tidepool.keycloak.extensions.broker;

import org.keycloak.broker.oidc.OIDCIdentityProvider;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.models.KeycloakSession;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;

public class SMARTIdentityProvider extends OIDCIdentityProvider {

private static final String[] defaultForwardParameters = {"launch", "aud", "iss"};

private final String fhirVersion;

public SMARTIdentityProvider(KeycloakSession session, SMARTIdentityProviderConfig config) {
super(session, discoverConfig(session, config.getIssuer()));
getConfig().setIssuer(config.getIssuer());
getConfig().setClientId(config.getClientId());
getConfig().setClientSecret(config.getClientSecret());
getConfig().setDefaultScope(config.getScopes());
getConfig().setAlias(config.getAlias());
getConfig().setForwardParameters(withDefaultForwardParameters(config.getForwardParameters()));

fhirVersion = config.getFHIRVersion();
}

private static String withDefaultForwardParameters(String params){
if (params == null) {
params = "";
}

HashSet<String> set = new HashSet<>(Arrays.asList(params.split(",")));
set.addAll(Arrays.asList(defaultForwardParameters));
return String.join(",", set.stream().map(String::trim).toArray(String[]::new));
}

private static OIDCIdentityProviderConfig discoverConfig(KeycloakSession session, String issuer) {
OIDCIdentityProviderFactory factory = new OIDCIdentityProviderFactory();
OIDCIdentityProviderConfig identityProviderConfig = factory.createConfig();

if (issuer == null || issuer.isEmpty()) {
return identityProviderConfig;
}

if (!issuer.endsWith("/")) {
issuer = issuer + "/";
}

String smartConfigurationUrl = issuer + ".well-known/smart-configuration";
SimpleHttp request = SimpleHttp.doGet(smartConfigurationUrl, session).header("Accept", "application/fhir+json");

try {
SimpleHttp.Response response = request.asResponse();
if (response.getStatus() != 200) {
String msg = "failed to invoke url [" + smartConfigurationUrl + "]";
String tmp = response.asString();
if (tmp != null) msg = tmp;

throw new IdentityBrokerException("Failed to invoke url [" + smartConfigurationUrl + "]: " + msg);
}

identityProviderConfig.setConfig(factory.parseConfig(session, response.asString()));
} catch (IOException e) {
throw new IdentityBrokerException("Unable to retrieve smart configuration");
}

return identityProviderConfig;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.tidepool.keycloak.extensions.broker;

import org.keycloak.models.IdentityProviderModel;

public class SMARTIdentityProviderConfig extends IdentityProviderModel {

public SMARTIdentityProviderConfig(IdentityProviderModel identityProviderModel) {
super(identityProviderModel);
}

public SMARTIdentityProviderConfig() {
super();
}

public String getIssuer() {
return getConfig().get("issuer");
}

public void setIssuer(String issuer) {
getConfig().put("issuer", issuer);
}

public String getClientId() {
return getConfig().get("clientId");
}

public void setClientId(String clientId) {
getConfig().put("clientId", clientId);
}

public String getClientSecret() {
return getConfig().get("clientSecret");
}

public void setClientSecret(String clientSecret) {
getConfig().put("clientSecret", clientSecret);
}

public String getScopes() {
return getConfig().get("scopes");
}

public void setScopes(String scopes) {
getConfig().put("scopes", scopes);
}

public String getForwardParameters() {
return getConfig().get("forwardParameters");
}

public void setForwardParameters(String params) {
getConfig().put("forwardParameters", params);
}

public String getFHIRVersion() {
return getConfig().get("fhirVersion");
}

public void setFHIRVersion(String version) {
getConfig().put("fhirVersion", version);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.tidepool.keycloak.extensions.broker;

import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.util.JsonSerialization;

import java.io.IOException;
import java.util.List;
import java.util.Map;

public class SMARTIdentityProviderFactory extends AbstractIdentityProviderFactory<SMARTIdentityProvider> {

public static final String PROVIDER_ID = "smart";

public static final String[] SUPPORTED_FHIR_VERSIONS = {"R4"};

@Override
public String getName() {
return "SMART";
}

@Override
public SMARTIdentityProvider create(KeycloakSession session, IdentityProviderModel model) {
return new SMARTIdentityProvider(session, new SMARTIdentityProviderConfig(model));
}

@Override
public IdentityProviderModel createConfig() {
return new SMARTIdentityProviderConfig();
}

@Override
public Map<String, String> parseConfig(KeycloakSession session, String config) {
return parseSMARTConfig(session, config);
}

protected static Map<String, String> parseSMARTConfig(KeycloakSession session, String configString) {
OIDCConfigurationRepresentation rep;
try {
rep = JsonSerialization.readValue(configString, OIDCConfigurationRepresentation.class);
} catch (IOException e) {
throw new RuntimeException("failed to load openid connect metadata", e);
}
SMARTIdentityProviderConfig config = new SMARTIdentityProviderConfig();
config.setIssuer(rep.getIssuer());
return config.getConfig();
}

@Override
public List<ProviderConfigProperty> getConfigProperties() {
return ProviderConfigurationBuilder.create()
.property().name("issuer").label("Issuer").type(ProviderConfigProperty.STRING_TYPE).required(true).add()
.property().name("scopes").label("Scopes").type(ProviderConfigProperty.STRING_TYPE).add()
.property().name("forwardParameters").label("Forward Parameters").type(ProviderConfigProperty.STRING_TYPE).add()
.property().name("fhirVersion").label("FHIR Version").type(ProviderConfigProperty.LIST_TYPE).options(SUPPORTED_FHIR_VERSIONS).defaultValue(SUPPORTED_FHIR_VERSIONS[0]).required(true).add()
.build();
}

@Override
public String getId() {
return PROVIDER_ID;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.tidepool.keycloak.extensions.broker.SMARTIdentityProviderFactory

0 comments on commit a3b2998

Please sign in to comment.