Skip to content

Commit

Permalink
adding support for CLIENT_SECRET_JWT and PRIVATE_KEY_JWT client authe…
Browse files Browse the repository at this point in the history
…ntication method (MID-7488)
  • Loading branch information
skublik committed Jan 12, 2022
1 parent ae92473 commit 077cb6a
Show file tree
Hide file tree
Showing 34 changed files with 1,064 additions and 263 deletions.
4 changes: 4 additions & 0 deletions gui/admin-gui/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-saml2-service-provider</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.evolveum.midpoint.authentication.api.config.ModuleAuthentication;
import com.evolveum.midpoint.authentication.api.config.RemoteModuleAuthentication;
import com.evolveum.midpoint.authentication.api.util.AuthUtil;
import com.evolveum.midpoint.authentication.api.util.AuthenticationModuleNameConstants;
import com.evolveum.midpoint.web.component.form.MidpointForm;
import com.evolveum.midpoint.web.component.util.VisibleBehaviour;
import com.evolveum.midpoint.web.security.util.SecurityUtils;
Expand All @@ -22,10 +21,8 @@
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.IModel;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;

import java.io.Serializable;
import java.util.ArrayList;
Expand Down Expand Up @@ -56,22 +53,17 @@ protected void populateItem(ListItem<IdentityProvider> item) {
});
MidpointForm<?> form = new MidpointForm<>(ID_LOGOUT_FORM);
ModuleAuthentication actualModule = AuthUtil.getProcessingModuleIfExist();
form.add(new VisibleBehaviour(() -> existSamlAuthentication(actualModule)));
form.add(new VisibleBehaviour(() -> existRemoteAuthentication(actualModule)));
form.add(AttributeModifier.replace("action",
(IModel<String>) () -> existSamlAuthentication(actualModule) ?
(IModel<String>) () -> existRemoteAuthentication(actualModule) ?
SecurityUtils.getPathForLogoutWithContextPath(getRequest().getContextPath(), actualModule) : ""));
add(form);

WebMarkupContainer csrfField = SecurityUtils.createHiddenInputForCsrf(ID_CSRF_FIELD);
form.add(csrfField);
}

private boolean existSamlAuthentication(ModuleAuthentication actualModule) {
return AuthenticationModuleNameConstants.SAML_2.equals(actualModule.getPrefix())
&& (actualModule.getAuthentication() instanceof Saml2AuthenticationToken
|| (actualModule.getAuthentication() instanceof AnonymousAuthenticationToken
&& actualModule.getAuthentication().getDetails() instanceof Saml2AuthenticationToken));
}
abstract protected boolean existRemoteAuthentication(ModuleAuthentication actualModule);

private List<IdentityProvider> getProviders() {
List<IdentityProvider> providers = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@

import com.evolveum.midpoint.authentication.api.authorization.PageDescriptor;
import com.evolveum.midpoint.authentication.api.authorization.Url;
import com.evolveum.midpoint.authentication.api.config.ModuleAuthentication;
import com.evolveum.midpoint.authentication.api.util.AuthenticationModuleNameConstants;

import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;

import java.io.Serializable;

/**
Expand All @@ -23,4 +27,11 @@ public class PageOidcSelect extends AbstractPageRemoteAuthenticationSelect imple

public PageOidcSelect() {
}

protected boolean existRemoteAuthentication(ModuleAuthentication actualModule) {
return AuthenticationModuleNameConstants.OIDC.equals(actualModule.getNameOfModuleType())
&& (actualModule.getAuthentication() instanceof OAuth2LoginAuthenticationToken
|| (actualModule.getAuthentication() instanceof AnonymousAuthenticationToken
&& actualModule.getAuthentication().getDetails() instanceof OAuth2LoginAuthenticationToken));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
import java.io.Serializable;
import com.evolveum.midpoint.authentication.api.authorization.PageDescriptor;
import com.evolveum.midpoint.authentication.api.authorization.Url;
import com.evolveum.midpoint.authentication.api.config.ModuleAuthentication;
import com.evolveum.midpoint.authentication.api.util.AuthenticationModuleNameConstants;

import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;

/**
* @author skublik
*/
Expand All @@ -22,4 +26,11 @@ public class PageSamlSelect extends AbstractPageRemoteAuthenticationSelect imple

public PageSamlSelect() {
}

protected boolean existRemoteAuthentication(ModuleAuthentication actualModule) {
return AuthenticationModuleNameConstants.SAML_2.equals(actualModule.getNameOfModuleType())
&& (actualModule.getAuthentication() instanceof Saml2AuthenticationToken
|| (actualModule.getAuthentication() instanceof AnonymousAuthenticationToken
&& actualModule.getAuthentication().getDetails() instanceof Saml2AuthenticationToken));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -827,62 +827,125 @@
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="AbstractModuleSaml2KeyType" abstract="true">
<xsd:complexType name="ModuleSaml2KeyStoreKeyType">
<xsd:annotation>
<xsd:documentation>
Abstract SAML2 key.
SAML2 key from key store. Supported only JKS type of key store.
</xsd:documentation>
<xsd:appinfo>
<a:container/>
<a:since>4.1</a:since>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="tns:AbstractKeyStoreKeyType">
<xsd:sequence>
<xsd:element name="type" type="tns:ModuleSaml2KeyTypeType" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="AbstractKeyStoreKeyType">
<xsd:annotation>
<xsd:documentation>
Abstract type for key from key store. Supported only JKS type of key store.
</xsd:documentation>
<xsd:appinfo>
<a:container/>
<a:since>4.5</a:since>
</xsd:appinfo>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="type" type="tns:ModuleSaml2KeyTypeType" minOccurs="0" maxOccurs="1"/>
<xsd:element name="keyStorePath" type="xsd:string" minOccurs="1" maxOccurs="1"/>
<xsd:element name="keyStorePassword" type="t:ProtectedStringType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="keyAlias" type="xsd:string" minOccurs="1" maxOccurs="1"/>
<xsd:element name="keyPassword" type="t:ProtectedStringType" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="ModuleSaml2KeyStoreKeyType">
<xsd:complexType name="OidcKeyStoreKeyType">
<xsd:annotation>
<xsd:documentation>
SAML2 simple key.
OIDC key from key store. Supported only JKS type of key store.
</xsd:documentation>
<xsd:appinfo>
<a:container/>
<a:since>4.1</a:since>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="tns:AbstractModuleSaml2KeyType">
<xsd:extension base="tns:AbstractKeyStoreKeyType">
<xsd:sequence>
<xsd:element name="keyStorePath" type="xsd:string" minOccurs="1" maxOccurs="1"/>
<xsd:element name="keyStorePassword" type="t:ProtectedStringType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="keyAlias" type="xsd:string" minOccurs="1" maxOccurs="1"/>
<xsd:element name="keyPassword" type="t:ProtectedStringType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="keyId" type="xsd:string" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
The key ID of JWK is used to match a specific key.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="AbstractSimpleKeyType">
<xsd:annotation>
<xsd:documentation>
Abstract type for key from string representation.
</xsd:documentation>
<xsd:appinfo>
<a:container/>
<a:since>4.5</a:since>
</xsd:appinfo>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/>
<xsd:element name="privateKey" type="t:ProtectedStringType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="passphrase" type="t:ProtectedStringType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="certificate" type="t:ProtectedStringType" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="ModuleSaml2SimpleKeyType">
<xsd:complexType name="OidcSimpleKeyType">
<xsd:annotation>
<xsd:documentation>
OIDC key from string representation.
</xsd:documentation>
<xsd:appinfo>
<a:container/>
<a:since>4.5</a:since>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="tns:AbstractSimpleKeyType">
<xsd:sequence>
<xsd:element name="keyId" type="xsd:string" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
The key ID of JWK is used to match a specific key.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="ModuleSaml2SimpleKeyType">
<xsd:annotation>
<xsd:documentation>
SAML2 key from key store. Supported only JKS type of key store.
SAML2 key from string representation.
</xsd:documentation>
<xsd:appinfo>
<a:container/>
<a:since>4.1</a:since>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="tns:AbstractModuleSaml2KeyType">
<xsd:extension base="tns:AbstractSimpleKeyType">
<xsd:sequence>
<xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/>
<xsd:element name="privateKey" type="t:ProtectedStringType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="passphrase" type="t:ProtectedStringType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="certificate" type="t:ProtectedStringType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="type" type="tns:ModuleSaml2KeyTypeType" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
Expand Down Expand Up @@ -1098,6 +1161,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="clientSigningAlgorithm" type="xsd:string" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
Identifier of algorithm for digitally sign or create a MAC
of the content. (RFC7518 section-3.1)
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="scope" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
Expand All @@ -1121,6 +1192,22 @@
</xsd:annotation>
</xsd:element>
<xsd:element name="openIdProvider" type="tns:OidcOpenIdProviderType" minOccurs="1" maxOccurs="1"/>
<xsd:choice>
<xsd:element name="simpleProofKey" type="tns:OidcSimpleKeyType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Key used for sign with PRIVATE_KEY_JWT.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="keyStoreProofKey" type="tns:OidcKeyStoreKeyType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Key used for sign with PRIVATE_KEY_JWT.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice> <!--> can use only one of types, but all can be null<-->
</xsd:sequence>
</xsd:complexType>

Expand Down
4 changes: 4 additions & 0 deletions model/authentication-impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>

<!-- Test -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public UsernamePasswordAuthenticationToken authenticate(ConnectionEnvironment co
} else {
recordAuthenticationBehavior(principal.getUsername(), principal, connEnv, "password mismatch", authnCtx.getPrincipalType(), false);
recordPasswordAuthenticationFailure(principal, connEnv, getCredential(credentials), credentialsPolicy, "password mismatch", false);
throw new BadCredentialsException("web.security.provider.invalid");
throw new BadCredentialsException("web.security.provider.invalid.credentials");
}
}

Expand All @@ -150,7 +150,7 @@ public FocusType checkCredentials(ConnectionEnvironment connEnv, T authnCtx)
recordAuthenticationBehavior(principal.getUsername(), principal, connEnv, "password mismatch", authnCtx.getPrincipalType(), false);
recordPasswordAuthenticationFailure(principal, connEnv, getCredential(credentials), credentialsPolicy, "password mismatch", false);

throw new BadCredentialsException("web.security.provider.invalid");
throw new BadCredentialsException("web.security.provider.invalid.credentials");
}
}

Expand All @@ -160,7 +160,7 @@ private boolean checkCredentials(MidPointPrincipal principal, T authnCtx, Connec
CredentialsType credentials = focusType.getCredentials();
if (credentials == null || getCredential(credentials) == null) {
recordAuthenticationBehavior(principal.getUsername(), principal, connEnv, "no credentials in user", authnCtx.getPrincipalType(), false);
throw new AuthenticationCredentialsNotFoundException("web.security.provider.invalid");
throw new AuthenticationCredentialsNotFoundException("web.security.provider.invalid.credentials");
}

CredentialPolicyType credentialsPolicy = getCredentialsPolicy(principal, authnCtx);
Expand Down Expand Up @@ -215,7 +215,7 @@ public String getAndCheckUserPassword(ConnectionEnvironment connEnv, String user
CredentialsType credentials = focusType.getCredentials();
if (credentials == null) {
recordAuthenticationBehavior(username, null, connEnv, "no credentials in user", FocusType.class, false);
throw new AuthenticationCredentialsNotFoundException("web.security.provider.invalid");
throw new AuthenticationCredentialsNotFoundException("web.security.provider.invalid.credentials");
}
PasswordType passwordType = credentials.getPassword();
SecurityPolicyType securityPolicy = principal.getApplicableSecurityPolicy();
Expand Down Expand Up @@ -267,15 +267,15 @@ protected MidPointPrincipal getAndCheckPrincipal(ConnectionEnvironment connEnv,

if (StringUtils.isBlank(enteredUsername)) {
recordAuthenticationFailure(enteredUsername, connEnv, "no username");
throw new UsernameNotFoundException("web.security.provider.invalid");
throw new UsernameNotFoundException("web.security.provider.invalid.credentials");
}

MidPointPrincipal principal;
try {
principal = focusProfileService.getPrincipal(enteredUsername, clazz);
} catch (ObjectNotFoundException e) {
recordAuthenticationFailure(enteredUsername, connEnv, "no focus");
throw new UsernameNotFoundException("web.security.provider.invalid");
throw new UsernameNotFoundException("web.security.provider.invalid.credentials");
} catch (SchemaException e) {
recordAuthenticationFailure(enteredUsername, connEnv, "schema error");
throw new InternalAuthenticationServiceException("web.security.provider.invalid");
Expand All @@ -296,7 +296,7 @@ protected MidPointPrincipal getAndCheckPrincipal(ConnectionEnvironment connEnv,

if (principal == null) {
recordAuthenticationBehavior(enteredUsername, null, connEnv, "no focus", clazz, false);
throw new UsernameNotFoundException("web.security.provider.invalid");
throw new UsernameNotFoundException("web.security.provider.invalid.credentials");
}

if (supportsActivationCheck && !principal.isEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ public class PasswordAuthenticationEvaluatorImpl extends AuthenticationEvaluator
protected void checkEnteredCredentials(ConnectionEnvironment connEnv, PasswordAuthenticationContext authCtx) {
if (StringUtils.isBlank(authCtx.getUsername())) {
recordAuthenticationBehavior(authCtx.getUsername(), null, connEnv, "empty login provided", authCtx.getPrincipalType(), false);
throw new UsernameNotFoundException("web.security.provider.invalid");
throw new UsernameNotFoundException("web.security.provider.invalid.credentials");
}
if (StringUtils.isBlank(authCtx.getPassword())) {
recordAuthenticationBehavior(authCtx.getUsername(), null, connEnv, "empty password provided", authCtx.getPrincipalType(), false);
throw new BadCredentialsException("web.security.provider.invalid");
throw new BadCredentialsException("web.security.provider.invalid.credentials");
}
}

Expand Down

0 comments on commit 077cb6a

Please sign in to comment.