Skip to content

Commit

Permalink
Supporting customization of OIDC scopes in OIDCC4UI
Browse files Browse the repository at this point in the history
  • Loading branch information
ilgrosso committed Jul 24, 2023
1 parent ac46f44 commit 22467ff
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ protected WizardModel buildModelSteps(final Attr modelObject, final WizardModel

protected static class AttrStep extends WizardStep {

private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 8145346883748040158L;

AttrStep(final Attr modelObject) {
AjaxTextFieldPanel schema = new AjaxTextFieldPanel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,8 @@ protected ActionLinksTogglePanel<OIDCC4UIProviderTO> actionTogglePanel() {

@Override
public void updateHeader(final AjaxRequestTarget target, final Serializable object) {
if (object instanceof OIDCC4UIProviderTO) {
setHeader(target,
StringUtils.abbreviate(((OIDCC4UIProviderTO) object).getName(), HEADER_FIRST_ABBREVIATION));
if (object instanceof OIDCC4UIProviderTO provider) {
setHeader(target, StringUtils.abbreviate(provider.getName(), HEADER_FIRST_ABBREVIATION));
} else {
super.updateHeader(target, object);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.client.console.SyncopeConsoleSession;
import org.apache.syncope.client.console.SyncopeWebApplication;
import org.apache.syncope.client.console.panels.OIDCProvidersDirectoryPanel;
import org.apache.syncope.client.console.rest.ImplementationRestClient;
import org.apache.syncope.client.console.rest.OIDCProviderRestClient;
import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel;
import org.apache.syncope.client.console.wizards.mapping.ItemTransformersTogglePanel;
import org.apache.syncope.client.console.wizards.mapping.JEXLTransformersTogglePanel;
import org.apache.syncope.client.console.wizards.mapping.OIDCProviderMappingPanel;
Expand All @@ -42,6 +44,7 @@
import org.apache.syncope.common.lib.to.ImplementationTO;
import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO;
import org.apache.syncope.common.lib.types.OIDCClientImplementationType;
import org.apache.syncope.common.lib.types.OIDCScope;
import org.apache.wicket.PageReference;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.extensions.wizard.WizardModel;
Expand Down Expand Up @@ -107,11 +110,7 @@ protected Serializable onApplyInternal(final OIDCC4UIProviderTO modelObject) {
@Override
protected WizardModel buildModelSteps(final OIDCC4UIProviderTO modelObject, final WizardModel wizardModel) {
wizardModel.add(new OP(modelObject));
if (modelObject.getKey() == null) {
wizardModel.add(new OPContinue(modelObject));
} else {
wizardModel.add(new OPContinue(modelObject, true));
}
wizardModel.add(new OPContinue(modelObject, modelObject.getKey() != null));

Mapping mapping = new Mapping();
mapping.setOutputMarkupId(true);
Expand Down Expand Up @@ -145,6 +144,7 @@ protected void sendWarning(final String message) {
@Override
protected Future<Pair<Serializable, Serializable>> execute(
final Callable<Pair<Serializable, Serializable>> future) {

return SyncopeConsoleSession.get().execute(future);
}

Expand Down Expand Up @@ -200,132 +200,84 @@ public static class OPContinue extends WizardStep {

private static final long serialVersionUID = -7087008312629522790L;

public OPContinue(final OIDCC4UIProviderTO opTO) {
final WebMarkupContainer content = new WebMarkupContainer("content");
public OPContinue(final OIDCC4UIProviderTO opTO, final boolean readOnly) {
this.setOutputMarkupId(true);

WebMarkupContainer content = new WebMarkupContainer("content");
content.setOutputMarkupId(true);
add(content);

UrlValidator urlValidator = new UrlValidator();
final AjaxTextFieldPanel issuer = new AjaxTextFieldPanel(

AjaxTextFieldPanel issuer = new AjaxTextFieldPanel(
"issuer", "issuer", new PropertyModel<>(opTO, "issuer"));
issuer.addValidator(urlValidator);
issuer.addRequiredLabel();
content.add(issuer);
content.add(issuer.setReadOnly(readOnly));

final AjaxCheckBoxPanel hasDiscovery = new AjaxCheckBoxPanel(
AjaxCheckBoxPanel hasDiscovery = new AjaxCheckBoxPanel(
"hasDiscovery", "hasDiscovery", new PropertyModel<>(opTO, "hasDiscovery"));
content.add(hasDiscovery);

final AjaxTextFieldPanel authorizationEndpoint = new AjaxTextFieldPanel("authorizationEndpoint",
AjaxTextFieldPanel authorizationEndpoint = new AjaxTextFieldPanel("authorizationEndpoint",
"authorizationEndpoint", new PropertyModel<>(opTO, "authorizationEndpoint"));
authorizationEndpoint.addRequiredLabel();
authorizationEndpoint.addValidator(urlValidator);
content.add(authorizationEndpoint);
content.add(authorizationEndpoint.setReadOnly(readOnly));

final AjaxTextFieldPanel userinfoEndpoint = new AjaxTextFieldPanel("userinfoEndpoint",
AjaxTextFieldPanel userinfoEndpoint = new AjaxTextFieldPanel("userinfoEndpoint",
"userinfoEndpoint", new PropertyModel<>(opTO, "userinfoEndpoint"));
userinfoEndpoint.addValidator(urlValidator);
content.add(userinfoEndpoint);
content.add(userinfoEndpoint.setReadOnly(readOnly));

final AjaxTextFieldPanel tokenEndpoint = new AjaxTextFieldPanel("tokenEndpoint",
AjaxTextFieldPanel tokenEndpoint = new AjaxTextFieldPanel("tokenEndpoint",
"tokenEndpoint", new PropertyModel<>(opTO, "tokenEndpoint"));
tokenEndpoint.addRequiredLabel();
tokenEndpoint.addValidator(urlValidator);
content.add(tokenEndpoint);
content.add(tokenEndpoint.setReadOnly(readOnly));

final AjaxTextFieldPanel jwksUri = new AjaxTextFieldPanel("jwksUri",
AjaxTextFieldPanel jwksUri = new AjaxTextFieldPanel("jwksUri",
"jwksUri", new PropertyModel<>(opTO, "jwksUri"));
jwksUri.addRequiredLabel();
jwksUri.addValidator(urlValidator);
content.add(jwksUri);
content.add(jwksUri.setReadOnly(readOnly));

final AjaxTextFieldPanel endSessionEndpoint = new AjaxTextFieldPanel("endSessionEndpoint",
AjaxTextFieldPanel endSessionEndpoint = new AjaxTextFieldPanel("endSessionEndpoint",
"endSessionEndpoint", new PropertyModel<>(opTO, "endSessionEndpoint"));
endSessionEndpoint.addValidator(urlValidator);
content.add(endSessionEndpoint);
content.add(endSessionEndpoint.setReadOnly(readOnly));

final WebMarkupContainer visibleParam = new WebMarkupContainer("visibleParams");
visibleParam.setOutputMarkupPlaceholderTag(true);
visibleParam.add(authorizationEndpoint);
visibleParam.add(userinfoEndpoint);
visibleParam.add(tokenEndpoint);
visibleParam.add(jwksUri);
visibleParam.add(endSessionEndpoint);
content.add(visibleParam);
WebMarkupContainer visibleParams = new WebMarkupContainer("visibleParams");
visibleParams.setOutputMarkupPlaceholderTag(true);
visibleParams.add(authorizationEndpoint);
visibleParams.add(userinfoEndpoint);
visibleParams.add(tokenEndpoint);
visibleParams.add(jwksUri);
visibleParams.add(endSessionEndpoint);
content.add(visibleParams);

showHide(hasDiscovery, visibleParam);
showHide(hasDiscovery, visibleParams);

hasDiscovery.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {

private static final long serialVersionUID = -1107858522700306810L;

@Override
protected void onUpdate(final AjaxRequestTarget target) {
showHide(hasDiscovery, visibleParam);
target.add(visibleParam);
showHide(hasDiscovery, visibleParams);
target.add(visibleParams);
}
});
}

public OPContinue(final OIDCC4UIProviderTO opTO, final boolean readOnly) {
WebMarkupContainer content = new WebMarkupContainer("content");
this.setOutputMarkupId(true);
content.setOutputMarkupId(true);
add(content);

final AjaxTextFieldPanel issuer = new AjaxTextFieldPanel(
"issuer", "issuer", new PropertyModel<>(opTO, "issuer"));
issuer.setReadOnly(readOnly);
content.add(issuer);

final AjaxCheckBoxPanel hasDiscovery = new AjaxCheckBoxPanel(
"hasDiscovery", "hasDiscovery", new PropertyModel<>(opTO, "hasDiscovery"));
hasDiscovery.setReadOnly(readOnly);
content.add(hasDiscovery);

final AjaxTextFieldPanel authorizationEndpoint = new AjaxTextFieldPanel("authorizationEndpoint",
"authorizationEndpoint", new PropertyModel<>(opTO, "authorizationEndpoint"));
authorizationEndpoint.setReadOnly(readOnly);
content.add(authorizationEndpoint);

final AjaxTextFieldPanel userinfoEndpoint = new AjaxTextFieldPanel("userinfoEndpoint",
"userinfoEndpoint", new PropertyModel<>(opTO, "userinfoEndpoint"));
userinfoEndpoint.setReadOnly(readOnly);
content.add(userinfoEndpoint);

final AjaxTextFieldPanel tokenEndpoint = new AjaxTextFieldPanel("tokenEndpoint",
"tokenEndpoint", new PropertyModel<>(opTO, "tokenEndpoint"));
tokenEndpoint.setReadOnly(readOnly);
content.add(tokenEndpoint);

final AjaxTextFieldPanel jwksUri = new AjaxTextFieldPanel("jwksUri",
"jwksUri", new PropertyModel<>(opTO, "jwksUri"));
jwksUri.setReadOnly(readOnly);
content.add(jwksUri);

final AjaxTextFieldPanel endSessionEndpoint = new AjaxTextFieldPanel("endSessionEndpoint",
"endSessionEndpoint", new PropertyModel<>(opTO, "endSessionEndpoint"));
endSessionEndpoint.setReadOnly(readOnly);
content.add(endSessionEndpoint);

final WebMarkupContainer visibleParam = new WebMarkupContainer("visibleParams");
visibleParam.setOutputMarkupPlaceholderTag(true);
visibleParam.add(authorizationEndpoint);
visibleParam.add(userinfoEndpoint);
visibleParam.add(tokenEndpoint);
visibleParam.add(jwksUri);
visibleParam.add(endSessionEndpoint);
content.add(visibleParam);
AjaxTextFieldPanel value = new AjaxTextFieldPanel("panel", "scopes", new Model<>());
value.setChoices(Stream.of(OIDCScope.values()).map(s -> s.name().toLowerCase()).toList());
content.add(new MultiFieldPanel.Builder<String>(
new PropertyModel<>(opTO, "scopes")).build("scopes", "scopes", value));
}
}

private static void showHide(final AjaxCheckBoxPanel hasDiscovery, final WebMarkupContainer visibleParams) {
if (hasDiscovery.getField().getValue().equals("false")) {
visibleParams.setVisible(true);
} else {
visibleParams.setVisible(false);
}
visibleParams.setVisible("false".equals(hasDiscovery.getField().getValue()));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<span wicket:id="userinfoEndpoint">[userinfoEndpoint]</span>
<span wicket:id="endSessionEndpoint">[endSessionEndpoint]</span>
</div>

<span wicket:id="scopes"></span>
</div>
</wicket:panel>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class OIDCC4UIProviderTO extends ItemContainer implements EntityTO {

private String endSessionEndpoint;

private final List<String> scopes = new ArrayList<>();

private boolean hasDiscovery;

private boolean createUnmatching;
Expand Down Expand Up @@ -143,6 +145,12 @@ public void setEndSessionEndpoint(final String endSessionEndpoint) {
this.endSessionEndpoint = endSessionEndpoint;
}

@JacksonXmlElementWrapper(localName = "scopes")
@JacksonXmlProperty(localName = "scope")
public List<String> getScopes() {

Check notice

Code scanning / CodeQL

Exposing internal representation Note

getScopes exposes the internal representation stored in field scopes. The value may be modified
after this call to getScopes
.
getScopes exposes the internal representation stored in field scopes. The value may be modified
after this call to getScopes
.
getScopes exposes the internal representation stored in field scopes. The value may be modified
after this call to getScopes
.
getScopes exposes the internal representation stored in field scopes. The value may be modified
after this call to getScopes
.
return scopes;
}

public UserTO getUserTemplate() {
return userTemplate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat
// 2. get OpenID Connect tokens
String idTokenHint;
JWTClaimsSet idToken;
JWTClaimsSet accessToken;
try {
OidcCredentials credentials = new OidcCredentials();
credentials.setCode(new AuthorizationCode(authorizationCode));
Expand All @@ -149,6 +150,8 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat

idToken = credentials.getIdToken().getJWTClaimsSet();
idTokenHint = credentials.getIdToken().serialize();

accessToken = SignedJWT.parse(credentials.getAccessToken().getValue()).getJWTClaimsSet();
} catch (Exception e) {
LOG.error("While validating Token Response", e);
SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown);
Expand All @@ -166,9 +169,10 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat
Attr attrTO = new Attr();
attrTO.setSchema(item.getExtAttrName());

String value = idToken.getClaim(item.getExtAttrName()) == null
? null
: idToken.getClaim(item.getExtAttrName()).toString();
String value = Optional.ofNullable(idToken.getClaim(item.getExtAttrName())).
or(() -> Optional.ofNullable(accessToken.getClaim(item.getExtAttrName()))).
map(Object::toString).
orElse(null);
if (value != null) {
attrTO.getValues().add(value);
loginResponse.getAttrs().add(attrTO);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO;
import org.apache.syncope.core.persistence.api.entity.OIDCC4UIProvider;
import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
Expand Down Expand Up @@ -74,6 +75,7 @@ public static void importMetadata(final OIDCC4UIProviderTO opTO)
Optional.ofNullable(metadata.getUserInfoEndpointURI()).map(URI::toASCIIString).orElse(null));
opTO.setEndSessionEndpoint(
Optional.ofNullable(metadata.getEndSessionEndpointURI()).map(URI::toASCIIString).orElse(null));
Optional.ofNullable(metadata.getScopes()).ifPresent(s -> opTO.getScopes().addAll(s.toStringList()));
}

protected final List<OidcClient> cache = Collections.synchronizedList(new ArrayList<>());
Expand Down Expand Up @@ -103,7 +105,7 @@ public OidcClient add(final OIDCC4UIProvider op, final String callbackUrl) {
cfg.setDiscoveryURI(DISCOVERY_URI.apply(op.getIssuer()));
cfg.setPreferredJwsAlgorithm(JWSAlgorithm.HS256);
cfg.setOpMetadataResolver(new StaticOidcOpMetadataResolver(cfg, metadata));
cfg.setScope("openid profile email address phone offline_access");
cfg.setScope(op.getScopes().stream().collect(Collectors.joining(" ")));
cfg.setUseNonce(false);
cfg.setSessionLogoutHandler(new NoOpSessionLogoutHandler());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ public interface OIDCC4UIProvider extends Entity {

void setEndSessionEndpoint(String endSessionEndpoint);

List<String> getScopes();

void setScopes(List<String> scopes);

boolean getHasDiscovery();

void setHasDiscovery(boolean hasDiscovery);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,16 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.syncope.common.lib.to.Item;
import org.apache.syncope.common.lib.types.OIDCClientImplementationType;
import org.apache.syncope.core.persistence.api.entity.Implementation;
import org.apache.syncope.core.persistence.api.entity.OIDCC4UIProvider;
import org.apache.syncope.core.persistence.api.entity.OIDCC4UIUserTemplate;
import org.apache.syncope.core.persistence.jpa.validation.entity.OIDCC4UIProviderCheck;
import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
import org.springframework.util.CollectionUtils;

@Entity
@Table(name = JPAOIDCC4UIProvider.TABLE)
Expand Down Expand Up @@ -85,6 +88,8 @@ public class JPAOIDCC4UIProvider extends AbstractGeneratedKeyEntity implements O
@Column(nullable = true)
private String endSessionEndpoint;

private String scopes;

@Column(nullable = false)
private boolean hasDiscovery;

Expand Down Expand Up @@ -204,6 +209,18 @@ public void setEndSessionEndpoint(final String endSessionEndpoint) {
this.endSessionEndpoint = endSessionEndpoint;
}

@Override
public List<String> getScopes() {
return Optional.ofNullable(scopes).map(s -> Stream.of(s.split(" ")).toList()).orElse(List.of());
}

@Override
public void setScopes(final List<String> scopes) {
this.scopes = CollectionUtils.isEmpty(scopes)
? ""
: scopes.stream().collect(Collectors.joining(" "));
}

@Override
public boolean getHasDiscovery() {
return hasDiscovery;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public OIDCC4UIProvider update(final OIDCC4UIProvider op, final OIDCC4UIProvider
op.setTokenEndpoint(opTO.getTokenEndpoint());
op.setUserinfoEndpoint(opTO.getUserinfoEndpoint());
op.setEndSessionEndpoint(opTO.getEndSessionEndpoint());
op.setScopes(opTO.getScopes());
op.setHasDiscovery(opTO.getHasDiscovery());
op.setCreateUnmatching(opTO.isCreateUnmatching());
op.setSelfRegUnmatching(opTO.isSelfRegUnmatching());
Expand Down Expand Up @@ -243,6 +244,7 @@ public OIDCC4UIProviderTO getOIDCProviderTO(final OIDCC4UIProvider op) {
opTO.setTokenEndpoint(op.getTokenEndpoint());
opTO.setUserinfoEndpoint(op.getUserinfoEndpoint());
opTO.setEndSessionEndpoint(op.getEndSessionEndpoint());
opTO.getScopes().addAll(op.getScopes());
opTO.setHasDiscovery(op.getHasDiscovery());
opTO.setCreateUnmatching(op.isCreateUnmatching());
opTO.setSelfRegUnmatching(op.isSelfRegUnmatching());
Expand Down
Loading

0 comments on commit 22467ff

Please sign in to comment.