feat: 🎸 Refactored userinfo serv., new SAML-based claim sources
Refactored userinfo to fetch attributes only when needed and requested.
Also added the possibility to extract attributes from the actual SAML
session
BREAKING CHANGE: 🧨 requires database update
pull/1580/head
parent
13973560d9
commit
2c413d9916
|
@ -84,6 +84,7 @@ CREATE TABLE IF NOT EXISTS saved_user_auth (
|
||||||
acr VARCHAR(1024),
|
acr VARCHAR(1024),
|
||||||
name VARCHAR(1024),
|
name VARCHAR(1024),
|
||||||
authenticated BOOLEAN,
|
authenticated BOOLEAN,
|
||||||
|
authentication_attributes VARCHAR(2048)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS saved_user_auth_authority (
|
CREATE TABLE IF NOT EXISTS saved_user_auth_authority (
|
||||||
|
|
|
@ -82,7 +82,8 @@ CREATE TABLE IF NOT EXISTS saved_user_auth (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
acr VARCHAR(1024),
|
acr VARCHAR(1024),
|
||||||
name VARCHAR(1024),
|
name VARCHAR(1024),
|
||||||
authenticated BOOLEAN
|
authenticated BOOLEAN,
|
||||||
|
authentication_attributes TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS saved_user_auth_authority (
|
CREATE TABLE IF NOT EXISTS saved_user_auth_authority (
|
||||||
|
|
|
@ -83,7 +83,8 @@ CREATE TABLE IF NOT EXISTS saved_user_auth (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
acr VARCHAR(1024),
|
acr VARCHAR(1024),
|
||||||
name VARCHAR(1024),
|
name VARCHAR(1024),
|
||||||
authenticated BOOLEAN
|
authenticated BOOLEAN,
|
||||||
|
authentication_attributes TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS saved_user_auth_authority (
|
CREATE TABLE IF NOT EXISTS saved_user_auth_authority (
|
||||||
|
|
|
@ -17,20 +17,24 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||||
xmlns:context="http://www.springframework.org/schema/context"
|
xmlns:context="http://www.springframework.org/schema/context"
|
||||||
xmlns:security="http://www.springframework.org/schema/security"
|
xmlns:security="http://www.springframework.org/schema/security"
|
||||||
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
|
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
|
||||||
xmlns:util="http://www.springframework.org/schema/util"
|
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2
|
||||||
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
|
http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
|
||||||
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
|
http://www.springframework.org/schema/mvc
|
||||||
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd
|
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
|
||||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
http://www.springframework.org/schema/security
|
||||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd
|
http://www.springframework.org/schema/security/spring-security-4.2.xsd
|
||||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
|
http://www.springframework.org/schema/beans
|
||||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
|
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
||||||
|
http://www.springframework.org/schema/tx
|
||||||
|
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
|
||||||
|
http://www.springframework.org/schema/context
|
||||||
|
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
|
||||||
|
|
||||||
<!-- Scan for components -->
|
<!-- Scan for components -->
|
||||||
<context:component-scan annotation-config="true" base-package="cz.muni.ics" />
|
<context:component-scan annotation-config="true" base-package="cz.muni.ics" />
|
||||||
|
@ -57,10 +61,10 @@
|
||||||
<mvc:interceptor>
|
<mvc:interceptor>
|
||||||
<!-- Exclude APIs and other machine-facing endpoints from these interceptors -->
|
<!-- Exclude APIs and other machine-facing endpoints from these interceptors -->
|
||||||
<mvc:mapping path="/**" />
|
<mvc:mapping path="/**" />
|
||||||
|
<mvc:exclude-mapping path="/token**"/>
|
||||||
|
<mvc:exclude-mapping path="/resources/**" />
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint).URL}**" />
|
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint).URL}**" />
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" />
|
<mvc:exclude-mapping path="/#{T(cz.muni.ics.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" />
|
||||||
<mvc:exclude-mapping path="/resources/**" />
|
|
||||||
<mvc:exclude-mapping path="/token**"/>
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" />
|
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" />
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
|
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
|
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
|
||||||
|
@ -70,9 +74,26 @@
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).DEVICE_APPROVED_URL}**" />
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).DEVICE_APPROVED_URL}**" />
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
|
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
|
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.IsTestSpController).MAPPING}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.AupController).URL}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_MAPPING}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_AUTHORIZATION}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_ENSURE_VO_MAPPING}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_MANDATORY_VOS_GROUPS}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_PROD_VOS_GROUPS}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_TEST_VOS_GROUPS}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_LOGGED_IN}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_SPECIFIC_MAPPING}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedRegistrationController).REGISTRATION_CONTINUE_MAPPING}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedRegistrationController).REGISTRATION_FORM_MAPPING}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedRegistrationController).REGISTRATION_FORM_SUBMIT_MAPPING}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.RegistrationController).CONTINUE_DIRECT_MAPPING}**" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.LogoutController).MAPPING_SUCCESS}" />
|
||||||
|
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.LoginController).MAPPING_FAILURE}" />
|
||||||
|
<mvc:exclude-mapping path="/saml**" />
|
||||||
<!-- Inject the UserInfo into the response -->
|
<!-- Inject the UserInfo into the response -->
|
||||||
<ref bean="userInfoInterceptor"/>
|
<ref bean="userInfoInterceptor" />
|
||||||
</mvc:interceptor>
|
</mvc:interceptor>
|
||||||
<mvc:interceptor>
|
<mvc:interceptor>
|
||||||
<!-- Exclude APIs and other machine-facing endpoints from these interceptors -->
|
<!-- Exclude APIs and other machine-facing endpoints from these interceptors -->
|
||||||
|
|
|
@ -13,8 +13,3 @@
|
||||||
<li class="nav-header"><spring:message code="sidebar.personal.title"/></li>
|
<li class="nav-header"><spring:message code="sidebar.personal.title"/></li>
|
||||||
<li><a href="manage/#user/approved" data-toggle="collapse" data-target=".nav-collapse"><spring:message code="sidebar.personal.approved_sites"/></a></li>
|
<li><a href="manage/#user/approved" data-toggle="collapse" data-target=".nav-collapse"><spring:message code="sidebar.personal.approved_sites"/></a></li>
|
||||||
<li><a href="manage/#user/tokens" data-toggle="collapse" data-target=".nav-collapse"><spring:message code="sidebar.personal.active_tokens"/></a></li>
|
<li><a href="manage/#user/tokens" data-toggle="collapse" data-target=".nav-collapse"><spring:message code="sidebar.personal.active_tokens"/></a></li>
|
||||||
<li><a href="manage/#user/profile" data-toggle="collapse" data-target=".nav-collapse"><spring:message code="sidebar.personal.profile_information"/></a></li>
|
|
||||||
<li class="divider"></li>
|
|
||||||
<li class="nav-header"><spring:message code="sidebar.developer.title"/></li>
|
|
||||||
<li><a href="manage/#dev/dynreg" data-toggle="collapse" data-target=".nav-collapse"><spring:message code="sidebar.developer.client_registration"/></a><li>
|
|
||||||
<li><a href="manage/#dev/resource" data-toggle="collapse" data-target=".nav-collapse"><spring:message code="sidebar.developer.resource_registration"/></a><li>
|
|
|
@ -11,8 +11,3 @@
|
||||||
</div><!--/.well -->
|
</div><!--/.well -->
|
||||||
</div><!--/span-->
|
</div><!--/span-->
|
||||||
</security:authorize>
|
</security:authorize>
|
||||||
<security:authorize access="!hasRole('ROLE_USER')">
|
|
||||||
<div class="span1">
|
|
||||||
<!-- placeholder for non-logged-in users -->
|
|
||||||
</div><!--/span-->
|
|
||||||
</security:authorize>
|
|
||||||
|
|
|
@ -3,29 +3,6 @@
|
||||||
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%>
|
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%>
|
||||||
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
|
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
|
||||||
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
|
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
|
||||||
<c:choose>
|
|
||||||
<c:when test="${ not empty userInfo.preferredUsername }">
|
|
||||||
<c:set var="shortName" value="${ userInfo.preferredUsername }" />
|
|
||||||
</c:when>
|
|
||||||
<c:otherwise>
|
|
||||||
<c:set var="shortName" value="${ userInfo.sub }" />
|
|
||||||
</c:otherwise>
|
|
||||||
</c:choose>
|
|
||||||
<c:choose>
|
|
||||||
<c:when test="${ not empty userInfo.name }">
|
|
||||||
<c:set var="longName" value="${ userInfo.name }" />
|
|
||||||
</c:when>
|
|
||||||
<c:otherwise>
|
|
||||||
<c:choose>
|
|
||||||
<c:when test="${ not empty userInfo.givenName || not empty userInfo.familyName }">
|
|
||||||
<c:set var="longName" value="${ userInfo.givenName } ${ userInfo.familyName }" />
|
|
||||||
</c:when>
|
|
||||||
<c:otherwise>
|
|
||||||
<c:set var="longName" value="${ shortName }" />
|
|
||||||
</c:otherwise>
|
|
||||||
</c:choose>
|
|
||||||
</c:otherwise>
|
|
||||||
</c:choose>
|
|
||||||
<div class="navbar navbar-fixed-top">
|
<div class="navbar navbar-fixed-top">
|
||||||
<div class="navbar-inner">
|
<div class="navbar-inner">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -48,44 +25,27 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<security:authorize access="hasRole('ROLE_USER')">
|
<security:authorize access="hasRole('ROLE_USER')">
|
||||||
|
|
||||||
<ul class="nav hidden-desktop">
|
<ul class="nav hidden-desktop">
|
||||||
<o:actionmenu />
|
<o:actionmenu />
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</security:authorize>
|
</security:authorize>
|
||||||
|
|
||||||
<!-- use a full user menu and button when not collapsed -->
|
<!-- use a full user menu and button when not collapsed -->
|
||||||
<ul class="nav pull-right visible-desktop">
|
<ul class="nav pull-right visible-desktop">
|
||||||
<security:authorize access="hasRole('ROLE_USER')">
|
<security:authorize access="hasRole('ROLE_USER')">
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a id="userButton" class="dropdown-toggle" data-toggle="dropdown" href=""><i class="icon-user icon-white"></i> ${ shortName } <span class="caret"></span></a>
|
<a id="userButton" class="dropdown-toggle" data-toggle="dropdown" href=""><i class="icon-user icon-white"></i> <security:authentication property="principal.username" /> <span class="caret"></span></a>
|
||||||
<ul class="dropdown-menu pull-right">
|
<ul class="dropdown-menu pull-right">
|
||||||
<li><a href="manage/#user/profile" data-toggle="collapse" data-target=".nav-collapse">${ longName }</a></li>
|
|
||||||
<li class="divider"></li>
|
|
||||||
<li><a href="" data-toggle="collapse" data-target=".nav-collapse" class="logoutLink"><i class="icon-remove"></i> <spring:message code="topbar.logout"/></a></li>
|
<li><a href="" data-toggle="collapse" data-target=".nav-collapse" class="logoutLink"><i class="icon-remove"></i> <spring:message code="topbar.logout"/></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</security:authorize>
|
</security:authorize>
|
||||||
<security:authorize access="!hasRole('ROLE_USER')">
|
|
||||||
<li>
|
|
||||||
<a id="loginButton" href="login" data-toggle="collapse" data-target=".nav-collapse"><i class="icon-lock icon-white"></i> <spring:message code="topbar.login"/></a>
|
|
||||||
</li>
|
|
||||||
</security:authorize>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- use a simplified user button system when collapsed -->
|
<!-- use a simplified user button system when collapsed -->
|
||||||
<ul class="nav hidden-desktop">
|
<ul class="nav hidden-desktop">
|
||||||
<security:authorize access="hasRole('ROLE_USER')">
|
<security:authorize access="hasRole('ROLE_USER')">
|
||||||
<li><a href="manage/#user/profile">${ longName }</a></li>
|
|
||||||
<li class="divider"></li>
|
|
||||||
<li><a href="" class="logoutLink"><i class="icon-remove"></i> <spring:message code="topbar.logout"/></a></li>
|
<li><a href="" class="logoutLink"><i class="icon-remove"></i> <spring:message code="topbar.logout"/></a></li>
|
||||||
</security:authorize>
|
</security:authorize>
|
||||||
<security:authorize access="!hasRole('ROLE_USER')">
|
|
||||||
<li>
|
|
||||||
<a href="login" data-toggle="collapse" data-target=".nav-collapse"><i class="icon-lock"></i> <spring:message code="topbar.login"/></a>
|
|
||||||
</li>
|
|
||||||
</security:authorize>
|
|
||||||
</ul>
|
</ul>
|
||||||
<form action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }logout" method="POST" class="hidden" id="logoutForm">
|
<form action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }logout" method="POST" class="hidden" id="logoutForm">
|
||||||
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
|
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
xmlns:security="http://www.springframework.org/schema/security"
|
xmlns:security="http://www.springframework.org/schema/security"
|
||||||
xmlns:context="http://www.springframework.org/schema/context"
|
xmlns:context="http://www.springframework.org/schema/context"
|
||||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
|
||||||
xsi:schemaLocation="http://www.springframework.org/schema/security
|
xsi:schemaLocation="http://www.springframework.org/schema/security
|
||||||
http://www.springframework.org/schema/security/spring-security.xsd
|
http://www.springframework.org/schema/security/spring-security.xsd
|
||||||
http://www.springframework.org/schema/beans
|
http://www.springframework.org/schema/beans
|
||||||
|
@ -15,9 +14,7 @@
|
||||||
http://www.springframework.org/schema/context
|
http://www.springframework.org/schema/context
|
||||||
http://www.springframework.org/schema/context/spring-context.xsd
|
http://www.springframework.org/schema/context/spring-context.xsd
|
||||||
http://www.springframework.org/schema/aop
|
http://www.springframework.org/schema/aop
|
||||||
http://www.springframework.org/schema/aop/spring-aop.xsd
|
http://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||||
http://www.springframework.org/schema/mvc
|
|
||||||
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
|
|
||||||
|
|
||||||
<context:property-placeholder properties-ref="nonOverwrittenAttributeProperties" ignore-unresolvable="true" order="0"/>
|
<context:property-placeholder properties-ref="nonOverwrittenAttributeProperties" ignore-unresolvable="true" order="0"/>
|
||||||
<context:property-placeholder properties-ref="userAttrMappingsProperties" ignore-unresolvable="true" order="1"/>
|
<context:property-placeholder properties-ref="userAttrMappingsProperties" ignore-unresolvable="true" order="1"/>
|
||||||
|
@ -31,67 +28,6 @@
|
||||||
|
|
||||||
<aop:aspectj-autoproxy proxy-target-class="true"/>
|
<aop:aspectj-autoproxy proxy-target-class="true"/>
|
||||||
|
|
||||||
<mvc:interceptors>
|
|
||||||
<mvc:interceptor>
|
|
||||||
<mvc:mapping path="/**"/>
|
|
||||||
<ref bean="localeChangeInterceptor"/>
|
|
||||||
</mvc:interceptor>
|
|
||||||
<mvc:interceptor>
|
|
||||||
<!-- Exclude APIs and other machine-facing endpoints from these interceptors -->
|
|
||||||
<mvc:mapping path="/**" />
|
|
||||||
<mvc:exclude-mapping path="/token**"/>
|
|
||||||
<mvc:exclude-mapping path="/resources/**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint).URL}**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).ENDPOINT_URL}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).REQUEST_USER_CODE_URL}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).DEVICE_APPROVED_URL}**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.IsTestSpController).MAPPING}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.AupController).URL}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_MAPPING}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_AUTHORIZATION}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_ENSURE_VO_MAPPING}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_MANDATORY_VOS_GROUPS}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_PROD_VOS_GROUPS}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_TEST_VOS_GROUPS}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_LOGGED_IN}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_SPECIFIC_MAPPING}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedRegistrationController).REGISTRATION_CONTINUE_MAPPING}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedRegistrationController).REGISTRATION_FORM_MAPPING}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedRegistrationController).REGISTRATION_FORM_SUBMIT_MAPPING}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.RegistrationController).CONTINUE_DIRECT_MAPPING}**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.LogoutController).MAPPING_SUCCESS}" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.LoginController).MAPPING_FAILURE}" />
|
|
||||||
<mvc:exclude-mapping path="/saml**" />
|
|
||||||
<!-- Inject the UserInfo into the response -->
|
|
||||||
<ref bean="userInfoInterceptor" />
|
|
||||||
</mvc:interceptor>
|
|
||||||
<mvc:interceptor>
|
|
||||||
<!-- Exclude APIs and other machine-facing endpoints from these interceptors -->
|
|
||||||
<mvc:mapping path="/**" />
|
|
||||||
<mvc:exclude-mapping path="/token**"/>
|
|
||||||
<mvc:exclude-mapping path="/resources/**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint).URL}**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" />
|
|
||||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).ENDPOINT_URL}**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
|
|
||||||
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
|
|
||||||
<!-- Inject the server configuration into the response -->
|
|
||||||
<ref bean="serverConfigInterceptor"/>
|
|
||||||
</mvc:interceptor>
|
|
||||||
</mvc:interceptors>
|
|
||||||
|
|
||||||
<!-- default config values, by default override in file /etc/perun/perun-mitreid.properties -->
|
<!-- default config values, by default override in file /etc/perun/perun-mitreid.properties -->
|
||||||
<bean id="defaultCoreProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
|
<bean id="defaultCoreProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
|
||||||
<property name="properties">
|
<property name="properties">
|
||||||
|
@ -217,7 +153,7 @@
|
||||||
<bean id="nonOverwrittenAttributeProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
|
<bean id="nonOverwrittenAttributeProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
|
||||||
<property name="properties">
|
<property name="properties">
|
||||||
<props>
|
<props>
|
||||||
<prop key="user.attribute_names.fixedList">openid_sub,profile_preferred_username,profile_given_name,profile_middle_name,profile_family_name,profile_name,profile_zoneinfo,profile_locale,email_email,address_address_formatted,phone_phone,aups</prop>
|
<prop key="user.attribute_names.fixedList">openid_sub,profile_name,profile_preferred_username,profile_nickname,profile_given_name,profile_family_name,profile_middle_name,profile_zoneinfo,profile_locale,profile_birthdate,profile_gender,profile_picture,profile_profile,profile_website,email_email,email_email_verified,phone_phone,phone_phone_verified,address_address_formatted,address_country,address_locality,address_postal_code,address_region,address_street_address,aups</prop>
|
||||||
<prop key="facility.attribute_names.fixedList">checkGroupMembership,allowRegistration,registrationUrl,dynamicRegistration,clientId,voShortNames,wayfFilter,wayfEFilter,requestedAups,capabilities,testSp</prop>
|
<prop key="facility.attribute_names.fixedList">checkGroupMembership,allowRegistration,registrationUrl,dynamicRegistration,clientId,voShortNames,wayfFilter,wayfEFilter,requestedAups,capabilities,testSp</prop>
|
||||||
<prop key="group.attribute_names.fixedList"/>
|
<prop key="group.attribute_names.fixedList"/>
|
||||||
<prop key="vo.attribute_names.fixedList">aup</prop>
|
<prop key="vo.attribute_names.fixedList">aup</prop>
|
||||||
|
@ -231,9 +167,11 @@
|
||||||
<props>
|
<props>
|
||||||
<prop key="user.attribute_names.customList"/>
|
<prop key="user.attribute_names.customList"/>
|
||||||
<!-- ATTRIBUTES MAPPINGS -->
|
<!-- ATTRIBUTES MAPPINGS -->
|
||||||
|
<!-- Scope Openid -->
|
||||||
<prop key="openid_sub.mapping.ldap">login;x-ns-einfraid-persistent-shadow</prop>
|
<prop key="openid_sub.mapping.ldap">login;x-ns-einfraid-persistent-shadow</prop>
|
||||||
<prop key="openid_sub.mapping.rpc">urn:perun:user:attribute-def:core:id</prop>
|
<prop key="openid_sub.mapping.rpc">urn:perun:user:attribute-def:core:id</prop>
|
||||||
<prop key="openid_sub.type">STRING</prop>
|
<prop key="openid_sub.type">STRING</prop>
|
||||||
|
<!-- Scope Profile -->
|
||||||
<prop key="profile_preferred_username.mapping.ldap">login;x-ns-einfra</prop>
|
<prop key="profile_preferred_username.mapping.ldap">login;x-ns-einfra</prop>
|
||||||
<prop key="profile_preferred_username.mapping.rpc">urn:perun:user:attribute-def:def:login-namespace:einfra</prop>
|
<prop key="profile_preferred_username.mapping.rpc">urn:perun:user:attribute-def:def:login-namespace:einfra</prop>
|
||||||
<prop key="profile_preferred_username.type">STRING</prop>
|
<prop key="profile_preferred_username.type">STRING</prop>
|
||||||
|
@ -255,15 +193,58 @@
|
||||||
<prop key="profile_locale.mapping.ldap">preferredLanguage</prop>
|
<prop key="profile_locale.mapping.ldap">preferredLanguage</prop>
|
||||||
<prop key="profile_locale.mapping.rpc">urn:perun:user:attribute-def:def:preferredLanguage</prop>
|
<prop key="profile_locale.mapping.rpc">urn:perun:user:attribute-def:def:preferredLanguage</prop>
|
||||||
<prop key="profile_locale.type">STRING</prop>
|
<prop key="profile_locale.type">STRING</prop>
|
||||||
|
<prop key="profile_nickname.mapping.ldap"/>
|
||||||
|
<prop key="profile_nickname.mapping.rpc"/>
|
||||||
|
<prop key="profile_nickname.type"/>
|
||||||
|
<prop key="profile_birthdate.mapping.ldap"/>
|
||||||
|
<prop key="profile_birthdate.mapping.rpc"/>
|
||||||
|
<prop key="profile_birthdate.type"/>
|
||||||
|
<prop key="profile_gender.mapping.ldap"/>
|
||||||
|
<prop key="profile_gender.mapping.rpc"/>
|
||||||
|
<prop key="profile_gender.type"/>
|
||||||
|
<prop key="profile_picture.mapping.ldap"/>
|
||||||
|
<prop key="profile_picture.mapping.rpc"/>
|
||||||
|
<prop key="profile_picture.type"/>
|
||||||
|
<prop key="profile_profile.mapping.ldap"/>
|
||||||
|
<prop key="profile_profile.mapping.rpc"/>
|
||||||
|
<prop key="profile_profile.type"/>
|
||||||
|
<prop key="profile_website.mapping.ldap"/>
|
||||||
|
<prop key="profile_website.mapping.rpc"/>
|
||||||
|
<prop key="profile_website.type"/>
|
||||||
|
<!-- Scope Email -->
|
||||||
<prop key="email_email.mapping.ldap">preferredMail</prop>
|
<prop key="email_email.mapping.ldap">preferredMail</prop>
|
||||||
<prop key="email_email.mapping.rpc">urn:perun:user:attribute-def:def:preferredMail</prop>
|
<prop key="email_email.mapping.rpc">urn:perun:user:attribute-def:def:preferredMail</prop>
|
||||||
<prop key="email_email.type">STRING</prop>
|
<prop key="email_email.type">STRING</prop>
|
||||||
|
<prop key="email_email_verified.mapping.ldap"/>
|
||||||
|
<prop key="email_email_verified.mapping.rpc"/>
|
||||||
|
<prop key="email_email_verified.type"/>
|
||||||
|
<!-- Scope Phone -->
|
||||||
<prop key="phone_phone.mapping.ldap">telephoneNumber</prop>
|
<prop key="phone_phone.mapping.ldap">telephoneNumber</prop>
|
||||||
<prop key="phone_phone.mapping.rpc">urn:perun:user:attribute-def:def:phone</prop>
|
<prop key="phone_phone.mapping.rpc">urn:perun:user:attribute-def:def:phone</prop>
|
||||||
<prop key="phone_phone.type">STRING</prop>
|
<prop key="phone_phone.type">STRING</prop>
|
||||||
|
<prop key="phone_phone_verified.mapping.ldap"/>
|
||||||
|
<prop key="phone_phone_verified.mapping.rpc"/>
|
||||||
|
<prop key="phone_phone_verified.type"/>
|
||||||
|
<!-- Scope Address -->
|
||||||
<prop key="address_address_formatted.mapping.ldap">postalAddress</prop>
|
<prop key="address_address_formatted.mapping.ldap">postalAddress</prop>
|
||||||
<prop key="address_address_formatted.mapping.rpc">urn:perun:user:attribute-def:def:address</prop>
|
<prop key="address_address_formatted.mapping.rpc">urn:perun:user:attribute-def:def:address</prop>
|
||||||
<prop key="address_address_formatted.type">STRING</prop>
|
<prop key="address_address_formatted.type">STRING</prop>
|
||||||
|
<prop key="address_country.mapping.ldap"/>
|
||||||
|
<prop key="address_country.mapping.rpc"/>
|
||||||
|
<prop key="address_country.type"/>
|
||||||
|
<prop key="address_locality.mapping.ldap"/>
|
||||||
|
<prop key="address_locality.mapping.rpc"/>
|
||||||
|
<prop key="address_locality.type"/>
|
||||||
|
<prop key="address_postal_code.mapping.ldap"/>
|
||||||
|
<prop key="address_postal_code.mapping.rpc"/>
|
||||||
|
<prop key="address_postal_code.type"/>
|
||||||
|
<prop key="address_region.mapping.ldap"/>
|
||||||
|
<prop key="address_region.mapping.rpc"/>
|
||||||
|
<prop key="address_region.type"/>
|
||||||
|
<prop key="address_street_address.mapping.ldap"/>
|
||||||
|
<prop key="address_street_address.mapping.rpc"/>
|
||||||
|
<prop key="address_street_address.type"/>
|
||||||
|
<!-- Attributes -->
|
||||||
<prop key="aups.mapping.ldap">aups</prop>
|
<prop key="aups.mapping.ldap">aups</prop>
|
||||||
<prop key="aups.mapping.rpc">urn:perun:user:attribute-def:def:aups</prop>
|
<prop key="aups.mapping.rpc">urn:perun:user:attribute-def:def:aups</prop>
|
||||||
<prop key="aups.type">MAP_KEY_VALUE</prop>
|
<prop key="aups.type">MAP_KEY_VALUE</prop>
|
||||||
|
@ -401,20 +382,48 @@
|
||||||
<property name="ignoreResourceNotFound" value="true"/>
|
<property name="ignoreResourceNotFound" value="true"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<bean id="openidMappings" class="cz.muni.ics.oidc.server.userInfo.mappings.OpenidMappings">
|
||||||
|
<property name="sub" value="openid_sub"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="profileMappings" class="cz.muni.ics.oidc.server.userInfo.mappings.ProfileMappings">
|
||||||
|
<property name="name" value="profile_name"/>
|
||||||
|
<property name="preferredUsername" value="profile_preferred_username"/>
|
||||||
|
<property name="nickname" value="profile_nickname"/>
|
||||||
|
<property name="givenName" value="profile_given_name"/>
|
||||||
|
<property name="familyName" value="profile_family_name"/>
|
||||||
|
<property name="middleName" value="profile_middle_name"/>
|
||||||
|
<property name="zoneinfo" value="profile_zoneinfo"/>
|
||||||
|
<property name="locale" value="profile_locale"/>
|
||||||
|
<property name="birthdate" value="profile_birthdate"/>
|
||||||
|
<property name="gender" value="profile_gender"/>
|
||||||
|
<property name="picture" value="profile_picture"/>
|
||||||
|
<property name="profile" value="profile_profile"/>
|
||||||
|
<property name="website" value="profile_website"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="emailMappings" class="cz.muni.ics.oidc.server.userInfo.mappings.EmailMappings">
|
||||||
|
<property name="email" value="email_email"/>
|
||||||
|
<property name="emailVerified" value="email_email_verified"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="phoneMappings" class="cz.muni.ics.oidc.server.userInfo.mappings.PhoneMappings">
|
||||||
|
<property name="phoneNumber" value="phone_phone"/>
|
||||||
|
<property name="phoneNumberVerified" value="phone_phone_verified"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="addressMappings" class="cz.muni.ics.oidc.server.userInfo.mappings.AddressMappings">
|
||||||
|
<property name="formatted" value="address_address_formatted"/>
|
||||||
|
<property name="country" value="address_country"/>
|
||||||
|
<property name="locality" value="address_locality"/>
|
||||||
|
<property name="postalCode" value="address_postal_code"/>
|
||||||
|
<property name="region" value="address_region"/>
|
||||||
|
<property name="streetAddress" value="address_street_address"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
<!-- defines our own user info service -->
|
<!-- defines our own user info service -->
|
||||||
<bean id="userInfoService" primary="true" class="cz.muni.ics.oidc.server.userInfo.PerunUserInfoService">
|
<bean id="userInfoService" primary="true" class="cz.muni.ics.oidc.server.userInfo.PerunUserInfoService">
|
||||||
<property name="perunAdapter" ref="perunAdapter"/>
|
<property name="perunAdapter" ref="perunAdapter"/>
|
||||||
<property name="subAttribute" value="openid_sub"/>
|
|
||||||
<property name="preferredUsernameAttribute" value="profile_preferred_username"/>
|
|
||||||
<property name="givenNameAttribute" value="profile_given_name"/>
|
|
||||||
<property name="familyNameAttribute" value="profile_family_name"/>
|
|
||||||
<property name="middleNameAttribute" value="profile_middle_name"/>
|
|
||||||
<property name="fullNameAttribute" value="profile_name"/>
|
|
||||||
<property name="emailAttribute" value="email_email"/>
|
|
||||||
<property name="addressAttribute" value="address_address_formatted"/>
|
|
||||||
<property name="phoneAttribute" value="phone_phone"/>
|
|
||||||
<property name="zoneinfoAttribute" value="profile_zoneinfo"/>
|
|
||||||
<property name="localeAttribute" value="profile_locale"/>
|
|
||||||
<property name="properties" ref="coreProperties"/>
|
<property name="properties" ref="coreProperties"/>
|
||||||
<property name="customClaimNames" value="#{'${custom.claims}'.split('\s*,\s*')}"/>
|
<property name="customClaimNames" value="#{'${custom.claims}'.split('\s*,\s*')}"/>
|
||||||
<property name="forceRegenerateUserinfoCustomClaims" value="#{'${force.regenerate.userinfo.custom.claims}'.split('\s*,\s*')}"/>
|
<property name="forceRegenerateUserinfoCustomClaims" value="#{'${force.regenerate.userinfo.custom.claims}'.split('\s*,\s*')}"/>
|
||||||
|
|
|
@ -133,11 +133,9 @@ public class DiscoveryEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserInfo extractUser(UriComponents resourceUri) {
|
private UserInfo extractUser(UriComponents resourceUri) {
|
||||||
UserInfo user = userService.getByEmailAddress(resourceUri.getUserInfo() + "@" + resourceUri.getHost());
|
String username = resourceUri.getUserInfo() + "@" + resourceUri.getHost();
|
||||||
if (user == null) {
|
//TODO: lookup username in Perun
|
||||||
user = userService.getByUsername(resourceUri.getUserInfo()); // first part is the username
|
return null;
|
||||||
}
|
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping("/" + OPENID_CONFIGURATION_URL)
|
@RequestMapping("/" + OPENID_CONFIGURATION_URL)
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package cz.muni.ics.oauth2.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AuthenticationStatement {
|
||||||
|
|
||||||
|
private List<String> authenticatingAuthorities;
|
||||||
|
private String authnContextClassRef;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
package cz.muni.ics.oauth2.model;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonNull;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.opensaml.saml2.core.Attribute;
|
||||||
|
import org.opensaml.saml2.core.AuthenticatingAuthority;
|
||||||
|
import org.opensaml.saml2.core.AuthnStatement;
|
||||||
|
import org.springframework.security.saml.SAMLCredential;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SamlAuthenticationDetails {
|
||||||
|
|
||||||
|
public static final String RELAY_STATE = "relayState";
|
||||||
|
public static final String LOCAL_ENTITY_ID = "localEntityId";
|
||||||
|
public static final String REMOTE_ENTITY_ID = "remoteEntityId";
|
||||||
|
public static final String ATTRIBUTES = "attributes";
|
||||||
|
public static final String AUTHN_STATEMENTS = "authnStatements";
|
||||||
|
public static final String AUTHN_CONTEXT_CLASS_REF = "authnContextClassRef";
|
||||||
|
public static final String AUTHENTICATING_AUTHORITIES = "authenticatingAuthorities";
|
||||||
|
|
||||||
|
private String relayState;
|
||||||
|
private String remoteEntityID;
|
||||||
|
private String localEntityID;
|
||||||
|
private Map<String, String[]> attributes;
|
||||||
|
private List<AuthenticationStatement> authnStatements;
|
||||||
|
|
||||||
|
public SamlAuthenticationDetails(SAMLCredential samlCredential) {
|
||||||
|
this.relayState = samlCredential.getRelayState();
|
||||||
|
this.remoteEntityID = samlCredential.getRemoteEntityID();
|
||||||
|
this.localEntityID = samlCredential.getLocalEntityID();
|
||||||
|
this.attributes = processAttributes(samlCredential);
|
||||||
|
this.authnStatements = processAuthnStatement(samlCredential);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AuthenticationStatement> processAuthnStatement(SAMLCredential samlCredential) {
|
||||||
|
List<AuthenticationStatement> authenticationStatements = new ArrayList<>();
|
||||||
|
List<AuthnStatement> samlAuthnStatements = samlCredential.getAuthenticationAssertion().getAuthnStatements();
|
||||||
|
if (samlAuthnStatements != null) {
|
||||||
|
for (AuthnStatement as : samlAuthnStatements) {
|
||||||
|
if (as == null || as.getAuthnContext() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<String> authenticatingAuthorities = new ArrayList<>();
|
||||||
|
List<AuthenticatingAuthority> authnAuthorities = as.getAuthnContext()
|
||||||
|
.getAuthenticatingAuthorities();
|
||||||
|
if (authnAuthorities != null) {
|
||||||
|
for (AuthenticatingAuthority aa : authnAuthorities) {
|
||||||
|
if (aa != null && StringUtils.hasText(aa.getURI())) {
|
||||||
|
authenticatingAuthorities.add(aa.getURI());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String authnContextClassRef = null;
|
||||||
|
if (as.getAuthnContext().getAuthnContextClassRef() != null &&
|
||||||
|
StringUtils.hasText(as.getAuthnContext().getAuthnContextClassRef().getAuthnContextClassRef()))
|
||||||
|
{
|
||||||
|
authnContextClassRef = as.getAuthnContext().getAuthnContextClassRef().getAuthnContextClassRef();
|
||||||
|
}
|
||||||
|
authenticationStatements.add(new AuthenticationStatement(authenticatingAuthorities, authnContextClassRef));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return authenticationStatements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String[]> processAttributes(SAMLCredential samlCredential) {
|
||||||
|
Map<String, String[]> attributes = new HashMap<>();
|
||||||
|
List<Attribute> samlAttributes = samlCredential.getAttributes();
|
||||||
|
if (samlAttributes != null) {
|
||||||
|
for (Attribute a: samlAttributes) {
|
||||||
|
if (a == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String name = a.getName();
|
||||||
|
String[] val = samlCredential.getAttributeAsStringArray(name);
|
||||||
|
attributes.put(name, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SamlAuthenticationDetails deserialize(String strJson) {
|
||||||
|
if (!StringUtils.hasText(strJson)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JsonObject json = (JsonObject) JsonParser.parseString(strJson);
|
||||||
|
SamlAuthenticationDetails details = new SamlAuthenticationDetails();
|
||||||
|
details.setRelayState(getStringOrNull(json.get(RELAY_STATE)));
|
||||||
|
details.setRemoteEntityID(getStringOrNull(json.get(REMOTE_ENTITY_ID)));
|
||||||
|
details.setLocalEntityID(getStringOrNull(json.get(LOCAL_ENTITY_ID)));
|
||||||
|
|
||||||
|
Map<String, String[]> attributes = new HashMap<>();
|
||||||
|
JsonObject attrs = json.getAsJsonObject(ATTRIBUTES);
|
||||||
|
for (Map.Entry<String, JsonElement> e: attrs.entrySet()) {
|
||||||
|
JsonArray elements = e.getValue().getAsJsonArray();
|
||||||
|
String[] val = new String[elements.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (JsonElement element: elements) {
|
||||||
|
val[i++] = getStringOrNull(element);
|
||||||
|
}
|
||||||
|
attributes.put(e.getKey(), val);
|
||||||
|
}
|
||||||
|
details.setAttributes(attributes);
|
||||||
|
|
||||||
|
List<AuthenticationStatement> authnStatements = new ArrayList<>();
|
||||||
|
JsonArray authStmts = json.getAsJsonArray(AUTHN_STATEMENTS);
|
||||||
|
for (JsonElement e: authStmts) {
|
||||||
|
JsonObject obj = e.getAsJsonObject();
|
||||||
|
JsonArray authoritiesArr = obj.getAsJsonArray(AUTHENTICATING_AUTHORITIES);
|
||||||
|
List<String> authorities = new ArrayList<>();
|
||||||
|
for (JsonElement authority: authoritiesArr) {
|
||||||
|
authorities.add(authority.getAsString());
|
||||||
|
}
|
||||||
|
String authnContextClassRef = getStringOrNull(obj.get(AUTHN_CONTEXT_CLASS_REF));
|
||||||
|
authnStatements.add(new AuthenticationStatement(authorities, authnContextClassRef));
|
||||||
|
}
|
||||||
|
details.setAuthnStatements(authnStatements);
|
||||||
|
|
||||||
|
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String serialize(SamlAuthenticationDetails o) {
|
||||||
|
if (o == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JsonObject object = new JsonObject();
|
||||||
|
addStringOrNull(object, RELAY_STATE, o.getRelayState());
|
||||||
|
addStringOrNull(object, LOCAL_ENTITY_ID, o.getLocalEntityID());
|
||||||
|
addStringOrNull(object, REMOTE_ENTITY_ID, o.getRemoteEntityID());
|
||||||
|
JsonObject attrs = new JsonObject();
|
||||||
|
for (Map.Entry<String, String[]> e: o.getAttributes().entrySet()) {
|
||||||
|
JsonArray val = new JsonArray();
|
||||||
|
for (String v: e.getValue()) {
|
||||||
|
if (v == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
val.add(v);
|
||||||
|
}
|
||||||
|
attrs.add(e.getKey(), val);
|
||||||
|
}
|
||||||
|
object.add(ATTRIBUTES, attrs);
|
||||||
|
JsonArray authnStatements = new JsonArray();
|
||||||
|
for (AuthenticationStatement as: o.getAuthnStatements()) {
|
||||||
|
JsonObject asJson = new JsonObject();
|
||||||
|
addStringOrNull(asJson, AUTHN_CONTEXT_CLASS_REF, as.getAuthnContextClassRef());
|
||||||
|
JsonArray authorities = new JsonArray();
|
||||||
|
for (String authAuthority: as.getAuthenticatingAuthorities()) {
|
||||||
|
if (authAuthority == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
authorities.add(authAuthority);
|
||||||
|
}
|
||||||
|
asJson.add(AUTHENTICATING_AUTHORITIES, asJson);
|
||||||
|
}
|
||||||
|
object.add(AUTHN_STATEMENTS, authnStatements);
|
||||||
|
return object.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addStringOrNull(JsonObject target, String key, String value) {
|
||||||
|
if (value == null) {
|
||||||
|
target.add(key, new JsonNull());
|
||||||
|
} else {
|
||||||
|
target.addProperty(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getStringOrNull(JsonElement jsonElement) {
|
||||||
|
if (jsonElement.isJsonPrimitive()) {
|
||||||
|
return jsonElement.getAsString();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package cz.muni.ics.oauth2.model;
|
package cz.muni.ics.oauth2.model;
|
||||||
|
|
||||||
|
import cz.muni.ics.oauth2.model.convert.JsonElementStringConverter;
|
||||||
|
import cz.muni.ics.oauth2.model.convert.SamlAuthenticationDetailsStringConverter;
|
||||||
import cz.muni.ics.oauth2.model.convert.SimpleGrantedAuthorityStringConverter;
|
import cz.muni.ics.oauth2.model.convert.SimpleGrantedAuthorityStringConverter;
|
||||||
import cz.muni.ics.oidc.saml.SamlPrincipal;
|
import cz.muni.ics.oidc.saml.SamlPrincipal;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -40,7 +42,6 @@ import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.eclipse.persistence.annotations.CascadeOnDelete;
|
import org.eclipse.persistence.annotations.CascadeOnDelete;
|
||||||
import org.opensaml.saml2.core.AuthnContext;
|
import org.opensaml.saml2.core.AuthnContext;
|
||||||
import org.opensaml.saml2.core.AuthnContextClassRef;
|
import org.opensaml.saml2.core.AuthnContextClassRef;
|
||||||
|
@ -48,6 +49,7 @@ import org.opensaml.saml2.core.AuthnStatement;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
|
import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
|
||||||
|
import org.springframework.security.saml.SAMLCredential;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class stands in for an original Authentication object.
|
* This class stands in for an original Authentication object.
|
||||||
|
@ -89,6 +91,10 @@ public class SavedUserAuthentication implements Authentication {
|
||||||
@Column(name = "acr")
|
@Column(name = "acr")
|
||||||
private String acr;
|
private String acr;
|
||||||
|
|
||||||
|
@Column(name = "authentication_attributes")
|
||||||
|
@Convert(converter = SamlAuthenticationDetailsStringConverter.class)
|
||||||
|
private SamlAuthenticationDetails authenticationDetails;
|
||||||
|
|
||||||
public SavedUserAuthentication(Authentication src) {
|
public SavedUserAuthentication(Authentication src) {
|
||||||
setName(src.getName());
|
setName(src.getName());
|
||||||
setAuthorities(new HashSet<>(src.getAuthorities()));
|
setAuthorities(new HashSet<>(src.getAuthorities()));
|
||||||
|
@ -104,6 +110,7 @@ public class SavedUserAuthentication implements Authentication {
|
||||||
.map(AuthnContext::getAuthnContextClassRef)
|
.map(AuthnContext::getAuthnContextClassRef)
|
||||||
.map(AuthnContextClassRef::getAuthnContextClassRef)
|
.map(AuthnContextClassRef::getAuthnContextClassRef)
|
||||||
.collect(Collectors.joining());
|
.collect(Collectors.joining());
|
||||||
|
this.authenticationDetails = new SamlAuthenticationDetails((SAMLCredential) src.getCredentials());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright 2018 The MIT Internet Trust Consortium
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
package cz.muni.ics.oauth2.model.convert;
|
||||||
|
|
||||||
|
import cz.muni.ics.oauth2.model.SamlAuthenticationDetails;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import javax.persistence.AttributeConverter;
|
||||||
|
import javax.persistence.Converter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates a Serializable object of certain primitive types
|
||||||
|
* into a String for storage in the database, for use with the
|
||||||
|
* OAuth2Request extensions map.
|
||||||
|
*
|
||||||
|
* This class does allow some extension data to be lost.
|
||||||
|
*
|
||||||
|
* @author jricher
|
||||||
|
*/
|
||||||
|
@Converter
|
||||||
|
@Slf4j
|
||||||
|
public class SamlAuthenticationDetailsStringConverter implements AttributeConverter<SamlAuthenticationDetails, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String convertToDatabaseColumn(SamlAuthenticationDetails attribute) {
|
||||||
|
return SamlAuthenticationDetails.serialize(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SamlAuthenticationDetails convertToEntityAttribute(String dbData) {
|
||||||
|
return SamlAuthenticationDetails.deserialize(dbData);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
|
||||||
import cz.muni.ics.oauth2.service.DeviceCodeService;
|
import cz.muni.ics.oauth2.service.DeviceCodeService;
|
||||||
import cz.muni.ics.oauth2.service.SystemScopeService;
|
import cz.muni.ics.oauth2.service.SystemScopeService;
|
||||||
import cz.muni.ics.oauth2.token.DeviceTokenGranter;
|
import cz.muni.ics.oauth2.token.DeviceTokenGranter;
|
||||||
|
import cz.muni.ics.oidc.saml.SamlPrincipal;
|
||||||
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
|
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
|
||||||
import cz.muni.ics.oidc.server.userInfo.PerunUserInfo;
|
import cz.muni.ics.oidc.server.userInfo.PerunUserInfo;
|
||||||
import cz.muni.ics.oidc.web.WebHtmlClasses;
|
import cz.muni.ics.oidc.web.WebHtmlClasses;
|
||||||
|
@ -56,13 +57,13 @@ import org.springframework.security.oauth2.provider.AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||||
import org.springframework.security.oauth2.provider.OAuth2Request;
|
import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||||
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
|
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
|
||||||
|
import org.springframework.security.saml.SAMLCredential;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.ModelMap;
|
import org.springframework.ui.ModelMap;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements https://tools.ietf.org/html/draft-ietf-oauth-device-flow
|
* Implements https://tools.ietf.org/html/draft-ietf-oauth-device-flow
|
||||||
|
@ -247,7 +248,7 @@ public class DeviceEndpoint {
|
||||||
@GetMapping(value = CHECK_USER_CODE_URL)
|
@GetMapping(value = CHECK_USER_CODE_URL)
|
||||||
public String startApproveDevice(@RequestParam(USER_CODE) String userCode,
|
public String startApproveDevice(@RequestParam(USER_CODE) String userCode,
|
||||||
ModelMap model,
|
ModelMap model,
|
||||||
Principal p,
|
Authentication auth,
|
||||||
HttpServletRequest req)
|
HttpServletRequest req)
|
||||||
{
|
{
|
||||||
DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode);
|
DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode);
|
||||||
|
@ -267,6 +268,8 @@ public class DeviceEndpoint {
|
||||||
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(dc.getRequestParameters());
|
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(dc.getRequestParameters());
|
||||||
session.setAttribute(AUTHORIZATION_REQUEST, authorizationRequest);
|
session.setAttribute(AUTHORIZATION_REQUEST, authorizationRequest);
|
||||||
|
|
||||||
|
SamlPrincipal p = (SamlPrincipal) auth.getPrincipal();
|
||||||
|
|
||||||
return getApproveDeviceViewName(model, p, req, dc);
|
return getApproveDeviceViewName(model, p, req, dc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,7 +277,6 @@ public class DeviceEndpoint {
|
||||||
@PostMapping(value = DEVICE_APPROVED_URL)
|
@PostMapping(value = DEVICE_APPROVED_URL)
|
||||||
public String processApproveDevice(@RequestParam(USER_CODE) String userCode,
|
public String processApproveDevice(@RequestParam(USER_CODE) String userCode,
|
||||||
@RequestParam(value = USER_OAUTH_APPROVAL) Boolean approve,
|
@RequestParam(value = USER_OAUTH_APPROVAL) Boolean approve,
|
||||||
Principal p,
|
|
||||||
HttpServletRequest req,
|
HttpServletRequest req,
|
||||||
ModelMap model,
|
ModelMap model,
|
||||||
Authentication auth)
|
Authentication auth)
|
||||||
|
@ -308,6 +310,7 @@ public class DeviceEndpoint {
|
||||||
// user did not approve
|
// user did not approve
|
||||||
if (!approve) {
|
if (!approve) {
|
||||||
model.addAttribute(APPROVED, false);
|
model.addAttribute(APPROVED, false);
|
||||||
|
SamlPrincipal p = (SamlPrincipal) auth.getPrincipal();
|
||||||
return getApproveDeviceViewName(model, p, req, dc);
|
return getApproveDeviceViewName(model, p, req, dc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,7 +405,7 @@ public class DeviceEndpoint {
|
||||||
return THEMED_DEVICE_APPROVED;
|
return THEMED_DEVICE_APPROVED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getApproveDeviceViewName(ModelMap model, Principal p, HttpServletRequest req, DeviceCode dc) {
|
private String getApproveDeviceViewName(ModelMap model, SamlPrincipal p, HttpServletRequest req, DeviceCode dc) {
|
||||||
if (perunOidcConfig.getTheme().equalsIgnoreCase(DEFAULT)) {
|
if (perunOidcConfig.getTheme().equalsIgnoreCase(DEFAULT)) {
|
||||||
model.put(SCOPES, ControllerUtils.getSortedScopes(dc.getScope(), scopeService));
|
model.put(SCOPES, ControllerUtils.getSortedScopes(dc.getScope(), scopeService));
|
||||||
return APPROVE_DEVICE;
|
return APPROVE_DEVICE;
|
||||||
|
@ -410,9 +413,11 @@ public class DeviceEndpoint {
|
||||||
|
|
||||||
ClientDetailsEntity client = (ClientDetailsEntity) model.get(CLIENT);
|
ClientDetailsEntity client = (ClientDetailsEntity) model.get(CLIENT);
|
||||||
|
|
||||||
PerunUserInfo user = (PerunUserInfo) userInfoService.getByUsernameAndClientId(
|
PerunUserInfo user = (PerunUserInfo) userInfoService.get(
|
||||||
p.getName(),
|
p.getUsername(),
|
||||||
client.getClientId()
|
client.getClientId(),
|
||||||
|
dc.getScope(),
|
||||||
|
p.getSamlCredential()
|
||||||
);
|
);
|
||||||
|
|
||||||
ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, dc.getScope(), user);
|
ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, dc.getScope(), user);
|
||||||
|
|
|
@ -141,7 +141,8 @@ public class IntrospectionEndpoint {
|
||||||
|
|
||||||
// get the user information of the user that authorized this token in the first place
|
// get the user information of the user that authorized this token in the first place
|
||||||
String userName = accessToken.getAuthenticationHolder().getAuthentication().getName();
|
String userName = accessToken.getAuthenticationHolder().getAuthentication().getName();
|
||||||
user = userInfoService.getByUsernameAndClientId(userName, tokenClient.getClientId());
|
user = userInfoService.get(userName, tokenClient.getClientId(),
|
||||||
|
authScopes, accessToken.getAuthenticationHolder().getUserAuth());
|
||||||
|
|
||||||
} catch (InvalidTokenException e) {
|
} catch (InvalidTokenException e) {
|
||||||
log.info("Invalid access token. Checking refresh token.");
|
log.info("Invalid access token. Checking refresh token.");
|
||||||
|
@ -154,7 +155,8 @@ public class IntrospectionEndpoint {
|
||||||
|
|
||||||
// get the user information of the user that authorized this token in the first place
|
// get the user information of the user that authorized this token in the first place
|
||||||
String userName = refreshToken.getAuthenticationHolder().getAuthentication().getName();
|
String userName = refreshToken.getAuthenticationHolder().getAuthentication().getName();
|
||||||
user = userInfoService.getByUsernameAndClientId(userName, tokenClient.getClientId());
|
user = userInfoService.get(userName, tokenClient.getClientId(), authScopes,
|
||||||
|
refreshToken.getAuthenticationHolder().getUserAuth());
|
||||||
|
|
||||||
} catch (InvalidTokenException e2) {
|
} catch (InvalidTokenException e2) {
|
||||||
log.error("Invalid refresh token");
|
log.error("Invalid refresh token");
|
||||||
|
|
|
@ -23,14 +23,13 @@ package cz.muni.ics.oauth2.web;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
||||||
import cz.muni.ics.oauth2.model.SystemScope;
|
import cz.muni.ics.oauth2.model.SystemScope;
|
||||||
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
|
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
|
||||||
import cz.muni.ics.oauth2.service.SystemScopeService;
|
import cz.muni.ics.oauth2.service.SystemScopeService;
|
||||||
|
import cz.muni.ics.oidc.saml.SamlPrincipal;
|
||||||
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
|
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
|
||||||
import cz.muni.ics.oidc.server.userInfo.PerunUserInfo;
|
|
||||||
import cz.muni.ics.oidc.web.WebHtmlClasses;
|
import cz.muni.ics.oidc.web.WebHtmlClasses;
|
||||||
import cz.muni.ics.oidc.web.controllers.ControllerUtils;
|
import cz.muni.ics.oidc.web.controllers.ControllerUtils;
|
||||||
import cz.muni.ics.openid.connect.model.UserInfo;
|
import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
|
@ -39,13 +38,11 @@ import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
|
||||||
import cz.muni.ics.openid.connect.service.UserInfoService;
|
import cz.muni.ics.openid.connect.service.UserInfoService;
|
||||||
import cz.muni.ics.openid.connect.view.HttpCodeView;
|
import cz.muni.ics.openid.connect.view.HttpCodeView;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -53,6 +50,7 @@ import org.apache.http.client.utils.URIBuilder;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
|
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
|
||||||
import org.springframework.security.oauth2.provider.AuthorizationRequest;
|
import org.springframework.security.oauth2.provider.AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.provider.endpoint.RedirectResolver;
|
import org.springframework.security.oauth2.provider.endpoint.RedirectResolver;
|
||||||
|
@ -60,8 +58,6 @@ import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.SessionAttributes;
|
import org.springframework.web.bind.annotation.SessionAttributes;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author jricher
|
* @author jricher
|
||||||
*
|
*
|
||||||
|
@ -121,24 +117,19 @@ public class OAuthConfirmationController {
|
||||||
this.htmlClasses = htmlClasses;
|
this.htmlClasses = htmlClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OAuthConfirmationController() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public OAuthConfirmationController(ClientDetailsEntityService clientService) {
|
public OAuthConfirmationController(ClientDetailsEntityService clientService) {
|
||||||
this.clientService = clientService;
|
this.clientService = clientService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_USER')")
|
@PreAuthorize("hasRole('ROLE_USER')")
|
||||||
@RequestMapping("/oauth/confirm_access")
|
@RequestMapping("/oauth/confirm_access")
|
||||||
public String confirmAccess(Map<String, Object> model, HttpServletRequest req, Principal p) {
|
public String confirmAccess(Map<String, Object> model, HttpServletRequest req, Authentication auth) {
|
||||||
|
|
||||||
AuthorizationRequest authRequest = (AuthorizationRequest) model.get(AUTHORIZATION_REQUEST);
|
AuthorizationRequest authRequest = (AuthorizationRequest) model.get(AUTHORIZATION_REQUEST);
|
||||||
// Check the "prompt" parameter to see if we need to do special processing
|
// Check the "prompt" parameter to see if we need to do special processing
|
||||||
|
|
||||||
String prompt = (String)authRequest.getExtensions().get(ConnectRequestParameters.PROMPT);
|
String prompt = (String)authRequest.getExtensions().get(ConnectRequestParameters.PROMPT);
|
||||||
List<String> prompts = Splitter.on(ConnectRequestParameters.PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt));
|
List<String> prompts = Splitter.on(ConnectRequestParameters.PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt));
|
||||||
ClientDetailsEntity client = null;
|
ClientDetailsEntity client;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
client = clientService.loadClientByClientId(authRequest.getClientId());
|
client = clientService.loadClientByClientId(authRequest.getClientId());
|
||||||
|
@ -167,27 +158,33 @@ public class OAuthConfirmationController {
|
||||||
model.put(CLIENT, client);
|
model.put(CLIENT, client);
|
||||||
model.put(REDIRECT_URI, authRequest.getRedirectUri());
|
model.put(REDIRECT_URI, authRequest.getRedirectUri());
|
||||||
model.put(REMEMBER_ENABLED, !prompts.contains(CONSENT));
|
model.put(REMEMBER_ENABLED, !prompts.contains(CONSENT));
|
||||||
|
model.put(GRAS, true);
|
||||||
Set<SystemScope> sortedScopes = ControllerUtils.getSortedScopes(authRequest.getScope(), scopeService);
|
|
||||||
model.put(SCOPES, sortedScopes);
|
|
||||||
|
|
||||||
// get the userinfo claims for each scope
|
// get the userinfo claims for each scope
|
||||||
model.put(CLAIMS, getClaimsForScopes(p, sortedScopes));
|
|
||||||
|
// contacts
|
||||||
|
if (client.getContacts() != null) {
|
||||||
|
String contacts = Joiner.on(", ").join(client.getContacts());
|
||||||
|
model.put(CONTACTS, contacts);
|
||||||
|
}
|
||||||
|
|
||||||
|
SamlPrincipal p = (SamlPrincipal) auth.getPrincipal();
|
||||||
|
UserInfo user = userInfoService.get(p.getUsername(), client.getClientId(), authRequest.getScope(), p.getSamlCredential());
|
||||||
|
|
||||||
// contacts
|
// contacts
|
||||||
if (client.getContacts() != null) {
|
if (client.getContacts() != null) {
|
||||||
model.put(CONTACTS, Joiner.on(", ").join(client.getContacts()));
|
model.put(CONTACTS, Joiner.on(", ").join(client.getContacts()));
|
||||||
}
|
}
|
||||||
model.put(GRAS, true);
|
|
||||||
|
|
||||||
if (perunOidcConfig.getTheme().equalsIgnoreCase(DEFAULT)) {
|
if (perunOidcConfig.getTheme().equalsIgnoreCase(DEFAULT)) {
|
||||||
|
Set<SystemScope> sortedScopes = ControllerUtils.getSortedScopes(authRequest.getScope(), scopeService);
|
||||||
|
model.put(SCOPES, sortedScopes);
|
||||||
|
model.put(CLAIMS, getClaimsForScopes(user, sortedScopes));
|
||||||
return APPROVE;
|
return APPROVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
PerunUserInfo perunUser = (PerunUserInfo) userInfoService.getByUsernameAndClientId(
|
|
||||||
p.getName(), client.getClientId());
|
|
||||||
ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, authRequest.getScope(),
|
ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, authRequest.getScope(),
|
||||||
perunUser);
|
user);
|
||||||
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
|
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
|
||||||
|
|
||||||
model.put(PAGE, CONSENT);
|
model.put(PAGE, CONSENT);
|
||||||
|
@ -214,8 +211,7 @@ public class OAuthConfirmationController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Map<String, String>> getClaimsForScopes(Principal p, Set<SystemScope> sortedScopes) {
|
private Map<String, Map<String, String>> getClaimsForScopes(UserInfo user, Set<SystemScope> sortedScopes) {
|
||||||
UserInfo user = userInfoService.getByUsername(p.getName());
|
|
||||||
Map<String, Map<String, String>> claimsForScopes = new HashMap<>();
|
Map<String, Map<String, String>> claimsForScopes = new HashMap<>();
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service providing methods to use AttributeMapping objects when fetching attributes.
|
* Service providing methods to use AttributeMapping objects when fetching attributes.
|
||||||
|
@ -93,8 +94,10 @@ public class AttributeMappingsService {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
AttributeMapping am = initAttrMapping(identifier, attrProperties);
|
AttributeMapping am = initAttrMapping(identifier, attrProperties);
|
||||||
log.debug("Initialized attributeMapping: {}", am);
|
if (am != null) {
|
||||||
attributeMap.put(am.getIdentifier(), am);
|
log.debug("Initialized attributeMapping: {}", am);
|
||||||
|
attributeMap.put(am.getIdentifier(), am);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +114,12 @@ public class AttributeMappingsService {
|
||||||
separator = attrProperties.getProperty(attrIdentifier + SEPARATOR);
|
separator = attrProperties.getProperty(attrIdentifier + SEPARATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(rpcIdentifier) && !StringUtils.hasText(ldapIdentifier)) {
|
||||||
|
log.warn("Attribute mapping for {} has no RPC nor LDAP mapping. It won't be used - check your configuration",
|
||||||
|
attrIdentifier);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return new AttributeMapping(attrIdentifier, rpcIdentifier, ldapIdentifier, type, separator);
|
return new AttributeMapping(attrIdentifier, rpcIdentifier, ldapIdentifier, type, separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,9 @@ public class PerunAccessTokenEnhancer implements TokenEnhancer {
|
||||||
UserInfo userInfo = null;
|
UserInfo userInfo = null;
|
||||||
if (originalAuthRequest.getScope().contains(SystemScopeService.OPENID_SCOPE)
|
if (originalAuthRequest.getScope().contains(SystemScopeService.OPENID_SCOPE)
|
||||||
&& !authentication.isClientOnly()) {
|
&& !authentication.isClientOnly()) {
|
||||||
userInfo = userInfoService.getByUsernameAndClientId(authentication.getName(), clientId);
|
userInfo = userInfoService.get(authentication.getName(), clientId,
|
||||||
|
accessToken.getScope(),
|
||||||
|
((OAuth2AccessTokenEntity) accessToken).getAuthenticationHolder().getUserAuth());
|
||||||
}
|
}
|
||||||
|
|
||||||
// create signed access token
|
// create signed access token
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cz.muni.ics.oidc.server;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonPrimitive;
|
import com.google.gson.JsonPrimitive;
|
||||||
import com.nimbusds.jose.shaded.json.JSONArray;
|
import com.nimbusds.jose.shaded.json.JSONArray;
|
||||||
import com.nimbusds.jose.util.JSONObjectUtils;
|
import com.nimbusds.jose.util.JSONObjectUtils;
|
||||||
|
@ -61,8 +62,9 @@ public class PerunOIDCTokenService extends DefaultOIDCTokenService {
|
||||||
Set<String> authorizedClaims = translator.getClaimsForScopeSet(scopes);
|
Set<String> authorizedClaims = translator.getClaimsForScopeSet(scopes);
|
||||||
Set<String> idTokenClaims = translator.getClaimsForScopeSet(perunOidcConfig.getIdTokenScopes());
|
Set<String> idTokenClaims = translator.getClaimsForScopeSet(perunOidcConfig.getIdTokenScopes());
|
||||||
|
|
||||||
for (Map.Entry<String, JsonElement> claim : userInfoService.getByUsernameAndClientId(userId,
|
JsonObject userInfoJson = userInfoService.get(userId, clientId, accessToken.getScope(), accessToken.getAuthenticationHolder().getUserAuth())
|
||||||
clientId).toJson().entrySet()) {
|
.toJson();
|
||||||
|
for (Map.Entry<String, JsonElement> claim : userInfoJson.entrySet()) {
|
||||||
String claimKey = claim.getKey();
|
String claimKey = claim.getKey();
|
||||||
JsonElement claimValue = claim.getValue();
|
JsonElement claimValue = claim.getValue();
|
||||||
if (claimValue != null && !claimValue.isJsonNull() && authorizedClaims.contains(claimKey)
|
if (claimValue != null && !claimValue.isJsonNull() && authorizedClaims.contains(claimKey)
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
package cz.muni.ics.oidc.server.claims;
|
|
||||||
|
|
||||||
import cz.muni.ics.oidc.models.Facility;
|
|
||||||
|
|
||||||
public class ClaimContextCommonParameters {
|
|
||||||
|
|
||||||
private Facility client;
|
|
||||||
|
|
||||||
public ClaimContextCommonParameters(Facility client) {
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Facility getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClient(Facility client) {
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,68 +1,41 @@
|
||||||
package cz.muni.ics.oidc.server.claims;
|
package cz.muni.ics.oidc.server.claims;
|
||||||
|
|
||||||
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
||||||
|
import cz.muni.ics.oauth2.model.SamlAuthenticationDetails;
|
||||||
|
import cz.muni.ics.oidc.models.Facility;
|
||||||
import cz.muni.ics.oidc.models.PerunAttributeValue;
|
import cz.muni.ics.oidc.models.PerunAttributeValue;
|
||||||
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
|
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context in which the value of the claim is produced.
|
* Context in which the value of the claim is produced.
|
||||||
*
|
*
|
||||||
* @author Martin Kuba <makub@ics.muni.cz>
|
* @author Martin Kuba <makub@ics.muni.cz>
|
||||||
*/
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
public class ClaimSourceProduceContext {
|
public class ClaimSourceProduceContext {
|
||||||
|
|
||||||
private final long perunUserId;
|
private long perunUserId;
|
||||||
private final String sub;
|
private String sub;
|
||||||
private final Map<String, PerunAttributeValue> attrValues;
|
private Map<String, PerunAttributeValue> attrValues;
|
||||||
private final PerunAdapter perunAdapter;
|
private PerunAdapter perunAdapter;
|
||||||
private final ClientDetailsEntity client;
|
private ClientDetailsEntity client;
|
||||||
private final ClaimContextCommonParameters contextCommonParameters;
|
private Facility facility;
|
||||||
|
private SamlAuthenticationDetails samlAuthenticationDetails;
|
||||||
|
private Set<String> scopes;
|
||||||
|
|
||||||
public ClaimSourceProduceContext(long perunUserId,
|
|
||||||
String sub,
|
|
||||||
Map<String, PerunAttributeValue> attrValues,
|
|
||||||
PerunAdapter perunAdapter,
|
|
||||||
ClientDetailsEntity client,
|
|
||||||
ClaimContextCommonParameters contextCommonParameters)
|
|
||||||
{
|
|
||||||
this.perunUserId = perunUserId;
|
|
||||||
this.sub = sub;
|
|
||||||
this.attrValues = attrValues;
|
|
||||||
this.perunAdapter = perunAdapter;
|
|
||||||
this.client = client;
|
|
||||||
this.contextCommonParameters = contextCommonParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, PerunAttributeValue> getAttrValues() {
|
|
||||||
return attrValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getPerunUserId() {
|
|
||||||
return perunUserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSub() {
|
|
||||||
return sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PerunAdapter getPerunAdapter() {
|
|
||||||
return perunAdapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientDetailsEntity getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClaimContextCommonParameters getContextCommonParameters() {
|
|
||||||
return contextCommonParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ClaimSourceProduceContext{" +
|
|
||||||
"perunUserId=" + perunUserId +
|
|
||||||
", sub='" + sub + '\'' +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package cz.muni.ics.oidc.server.claims;
|
package cz.muni.ics.oidc.server.claims;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import java.util.List;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
public class ClaimUtils {
|
public class ClaimUtils {
|
||||||
|
@ -41,4 +44,27 @@ public class ClaimUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean fillBooleanPropertyOrDefaultVal(String suffix, ClaimSourceInitContext ctx, boolean defaultVal) {
|
||||||
|
return fillBooleanPropertyOrDefaultVal(ctx.getProperty(suffix, NO_VALUE), defaultVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean fillBooleanPropertyOrDefaultVal(String prop, boolean defaultVal) {
|
||||||
|
if (StringUtils.hasText(prop)) {
|
||||||
|
return Boolean.parseBoolean(prop);
|
||||||
|
} else {
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArrayNode listToArrayNode(List<String> list) {
|
||||||
|
ArrayNode res = JsonNodeFactory.instance.arrayNode();
|
||||||
|
if (list != null && !list.isEmpty()) {
|
||||||
|
for (String s : list) {
|
||||||
|
if (StringUtils.hasText(s)) {
|
||||||
|
res.add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class EntitlementExtendedClaimSource extends EntitlementSource {
|
||||||
@Override
|
@Override
|
||||||
public JsonNode produceValue(ClaimSourceProduceContext pctx) {
|
public JsonNode produceValue(ClaimSourceProduceContext pctx) {
|
||||||
Long userId = pctx.getPerunUserId();
|
Long userId = pctx.getPerunUserId();
|
||||||
Set<String> entitlements = produceEntitlementsExtended(pctx.getContextCommonParameters().getClient(),
|
Set<String> entitlements = produceEntitlementsExtended(pctx.getFacility(),
|
||||||
userId, pctx.getPerunAdapter());
|
userId, pctx.getPerunAdapter());
|
||||||
JsonNode result = convertResultStringsToJsonArray(entitlements);
|
JsonNode result = convertResultStringsToJsonArray(entitlements);
|
||||||
log.debug("{} - produced value for user({}): '{}'", getClaimName(), userId, result);
|
log.debug("{} - produced value for user({}): '{}'", getClaimName(), userId, result);
|
||||||
|
|
|
@ -83,7 +83,7 @@ public class EntitlementSource extends GroupNamesSource {
|
||||||
public JsonNode produceValue(ClaimSourceProduceContext pctx) {
|
public JsonNode produceValue(ClaimSourceProduceContext pctx) {
|
||||||
PerunAdapter perunAdapter = pctx.getPerunAdapter();
|
PerunAdapter perunAdapter = pctx.getPerunAdapter();
|
||||||
Long userId = pctx.getPerunUserId();
|
Long userId = pctx.getPerunUserId();
|
||||||
Facility facility = pctx.getContextCommonParameters().getClient();
|
Facility facility = pctx.getFacility();
|
||||||
Set<Group> userGroups = getUserGroupsOnFacility(facility, userId, perunAdapter);
|
Set<Group> userGroups = getUserGroupsOnFacility(facility, userId, perunAdapter);
|
||||||
Set<String> entitlements = produceEntitlements(facility, userGroups, userId, perunAdapter);
|
Set<String> entitlements = produceEntitlements(facility, userGroups, userId, perunAdapter);
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ public class GroupNamesSource extends ClaimSource {
|
||||||
|
|
||||||
protected Map<Long, String> produceGroupNames(ClaimSourceProduceContext pctx) {
|
protected Map<Long, String> produceGroupNames(ClaimSourceProduceContext pctx) {
|
||||||
log.trace("{} - produce group names with trimming 'members' part of the group names", getClaimName());
|
log.trace("{} - produce group names with trimming 'members' part of the group names", getClaimName());
|
||||||
Facility facility = pctx.getContextCommonParameters().getClient();
|
Facility facility = pctx.getFacility();
|
||||||
Set<Group> userGroups = getUserGroupsOnFacility(facility, pctx.getPerunUserId(), pctx.getPerunAdapter());
|
Set<Group> userGroups = getUserGroupsOnFacility(facility, pctx.getPerunUserId(), pctx.getPerunAdapter());
|
||||||
return getGroupIdToNameMap(userGroups, true);
|
return getGroupIdToNameMap(userGroups, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package cz.muni.ics.oidc.server.claims.sources;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import cz.muni.ics.oauth2.model.SamlAuthenticationDetails;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimSource;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimUtils;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class SamlAttributeClaimSource extends ClaimSource {
|
||||||
|
|
||||||
|
private static final String ATTRIBUTE = "attribute";
|
||||||
|
private static final String MULTI_VALUE = "isMultiValue";
|
||||||
|
private static final String NO_VALUE_AS_NULL = "noValueAsNull";
|
||||||
|
private static final String SEPARATOR = "separator";
|
||||||
|
|
||||||
|
private final String attributeName;
|
||||||
|
private final String separator;
|
||||||
|
private final boolean multiValue;
|
||||||
|
private final boolean noValueAsNull;
|
||||||
|
|
||||||
|
public SamlAttributeClaimSource(ClaimSourceInitContext ctx) {
|
||||||
|
super(ctx);
|
||||||
|
this.attributeName = ClaimUtils.fillStringMandatoryProperty(ATTRIBUTE, ctx, getClaimName());
|
||||||
|
this.multiValue = ClaimUtils.fillBooleanPropertyOrDefaultVal(MULTI_VALUE, ctx, true);
|
||||||
|
this.noValueAsNull = ClaimUtils.fillBooleanPropertyOrDefaultVal(NO_VALUE_AS_NULL, ctx, false);
|
||||||
|
this.separator = ClaimUtils.fillStringPropertyOrDefaultVal(SEPARATOR, ctx, ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getAttrIdentifiers() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode produceValue(ClaimSourceProduceContext pctx) {
|
||||||
|
SamlAuthenticationDetails details = pctx.getSamlAuthenticationDetails();
|
||||||
|
if (details == null || details.getAttributes() == null || details.getAttributes().isEmpty()) {
|
||||||
|
return JsonNodeFactory.instance.nullNode();
|
||||||
|
}
|
||||||
|
String[] attrValue = details.getAttributes().getOrDefault(attributeName, null);
|
||||||
|
if (multiValue) {
|
||||||
|
if (attrValue == null || attrValue.length == 0) {
|
||||||
|
return !noValueAsNull ? JsonNodeFactory.instance.arrayNode() : JsonNodeFactory.instance.nullNode();
|
||||||
|
} else {
|
||||||
|
ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode();
|
||||||
|
for (String val: attrValue) {
|
||||||
|
arrayNode.add(val);
|
||||||
|
}
|
||||||
|
return arrayNode;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (attrValue == null || attrValue.length == 0) {
|
||||||
|
return JsonNodeFactory.instance.nullNode();
|
||||||
|
} else {
|
||||||
|
StringBuilder finalStr = new StringBuilder(separator);
|
||||||
|
for (String s: attrValue) {
|
||||||
|
finalStr.append(s);
|
||||||
|
}
|
||||||
|
return JsonNodeFactory.instance.textNode(finalStr.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package cz.muni.ics.oidc.server.claims.sources;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import cz.muni.ics.oauth2.model.AuthenticationStatement;
|
||||||
|
import cz.muni.ics.oauth2.model.SamlAuthenticationDetails;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimSource;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimUtils;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
public class SamlAuthnStatementClaimSource extends ClaimSource {
|
||||||
|
|
||||||
|
public SamlAuthnStatementClaimSource(ClaimSourceInitContext ctx) {
|
||||||
|
super(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getAttrIdentifiers() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode produceValue(ClaimSourceProduceContext pctx) {
|
||||||
|
SamlAuthenticationDetails details = pctx.getSamlAuthenticationDetails();
|
||||||
|
if (details == null || details.getAttributes() == null || details.getAttributes().isEmpty()) {
|
||||||
|
return JsonNodeFactory.instance.nullNode();
|
||||||
|
}
|
||||||
|
List<AuthenticationStatement> statements = details.getAuthnStatements();
|
||||||
|
if (statements == null || statements.isEmpty()) {
|
||||||
|
return JsonNodeFactory.instance.arrayNode();
|
||||||
|
} else {
|
||||||
|
ArrayNode res = JsonNodeFactory.instance.arrayNode();
|
||||||
|
for (AuthenticationStatement s: statements) {
|
||||||
|
if (s == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ObjectNode subNode = JsonNodeFactory.instance.objectNode();
|
||||||
|
subNode.put("authenticatingAuthorities", transformAuthenticatingAuthorities(s));
|
||||||
|
subNode.put("authnContextClassRef", transformAuthnContextClassRef(s));
|
||||||
|
res.add(subNode);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode transformAuthenticatingAuthorities(AuthenticationStatement s) {
|
||||||
|
if (s == null) {
|
||||||
|
return JsonNodeFactory.instance.arrayNode();
|
||||||
|
}
|
||||||
|
return ClaimUtils.listToArrayNode(s.getAuthenticatingAuthorities());
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode transformAuthnContextClassRef(AuthenticationStatement s) {
|
||||||
|
if (s == null) {
|
||||||
|
return JsonNodeFactory.instance.nullNode();
|
||||||
|
}
|
||||||
|
return StringUtils.hasText(s.getAuthnContextClassRef()) ?
|
||||||
|
JsonNodeFactory.instance.textNode(s.getAuthnContextClassRef()) : JsonNodeFactory.instance.nullNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,6 +9,8 @@ import com.google.gson.JsonSyntaxException;
|
||||||
import cz.muni.ics.openid.connect.model.DefaultUserInfo;
|
import cz.muni.ics.openid.connect.model.DefaultUserInfo;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,39 +18,41 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
*
|
*
|
||||||
* @author Martin Kuba <makub@ics.muni.cz>
|
* @author Martin Kuba <makub@ics.muni.cz>
|
||||||
*/
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class PerunUserInfo extends DefaultUserInfo {
|
public class PerunUserInfo extends DefaultUserInfo {
|
||||||
|
|
||||||
private final Map<String, JsonNode> customClaims = new LinkedHashMap<>();
|
private final Map<String, JsonNode> customClaims = new LinkedHashMap<>();
|
||||||
private JsonObject obj;
|
|
||||||
|
|
||||||
public Map<String, JsonNode> getCustomClaims() {
|
private long updatedAt;
|
||||||
return customClaims;
|
|
||||||
}
|
private JsonObject renderedObject;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonObject toJson() {
|
public JsonObject toJson() {
|
||||||
if (obj == null) {
|
//TODO: include updatedAd in the object
|
||||||
|
if (renderedObject == null) {
|
||||||
//delegate standard claims to DefaultUserInfo
|
//delegate standard claims to DefaultUserInfo
|
||||||
obj = super.toJson();
|
renderedObject = super.toJson();
|
||||||
//add custom claims
|
//add custom claims
|
||||||
for (Map.Entry<String, JsonNode> entry : customClaims.entrySet()) {
|
for (Map.Entry<String, JsonNode> entry : customClaims.entrySet()) {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
JsonNode value = entry.getValue();
|
JsonNode value = entry.getValue();
|
||||||
if (value == null || value.isNull()) {
|
if (value == null || value.isNull()) {
|
||||||
obj.addProperty(key, (String) null);
|
renderedObject.addProperty(key, (String) null);
|
||||||
log.debug("adding null claim {}=null", key);
|
log.debug("adding null claim {}=null", key);
|
||||||
} else if (value.isTextual() || value.isBoolean()) {
|
} else if (value.isTextual() || value.isBoolean()) {
|
||||||
obj.addProperty(key, value.asText());
|
renderedObject.addProperty(key, value.asText());
|
||||||
log.debug("adding string claim {}={}", key, value.asText());
|
log.debug("adding string claim {}={}", key, value.asText());
|
||||||
} else if (value.isNumber()) {
|
} else if (value.isNumber()) {
|
||||||
obj.addProperty(key, value.asLong());
|
renderedObject.addProperty(key, value.asLong());
|
||||||
log.debug("adding long claim {}={}", key, value.asText());
|
log.debug("adding long claim {}={}", key, value.asText());
|
||||||
} else if (value.isContainerNode()) {
|
} else if (value.isContainerNode()) {
|
||||||
try {
|
try {
|
||||||
//convert from Jackson to GSon
|
//convert from Jackson to GSon
|
||||||
String rawJson = new ObjectMapper().writeValueAsString(value);
|
String rawJson = new ObjectMapper().writeValueAsString(value);
|
||||||
obj.add(key, new JsonParser().parse(rawJson));
|
renderedObject.add(key, new JsonParser().parse(rawJson));
|
||||||
log.debug("adding JSON claim {}={}", key, rawJson);
|
log.debug("adding JSON claim {}={}", key, rawJson);
|
||||||
} catch (JsonProcessingException | JsonSyntaxException e) {
|
} catch (JsonProcessingException | JsonSyntaxException e) {
|
||||||
log.error("cannot convert Jackson/Gson value " + value, e);
|
log.error("cannot convert Jackson/Gson value " + value, e);
|
||||||
|
@ -60,7 +64,7 @@ public class PerunUserInfo extends DefaultUserInfo {
|
||||||
} else {
|
} else {
|
||||||
log.debug("already rendered to JSON");
|
log.debug("already rendered to JSON");
|
||||||
}
|
}
|
||||||
return obj;
|
return renderedObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,323 @@
|
||||||
|
package cz.muni.ics.oidc.server.userInfo;
|
||||||
|
|
||||||
|
import static cz.muni.ics.oidc.server.PerunScopeClaimTranslationService.ADDRESS;
|
||||||
|
import static cz.muni.ics.oidc.server.PerunScopeClaimTranslationService.EMAIL;
|
||||||
|
import static cz.muni.ics.oidc.server.PerunScopeClaimTranslationService.OPENID;
|
||||||
|
import static cz.muni.ics.oidc.server.PerunScopeClaimTranslationService.PHONE;
|
||||||
|
import static cz.muni.ics.oidc.server.PerunScopeClaimTranslationService.PROFILE;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import com.fasterxml.jackson.databind.node.TextNode;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
||||||
|
import cz.muni.ics.oidc.models.PerunAttributeValue;
|
||||||
|
import cz.muni.ics.oidc.models.PerunAttributeValueAwareModel;
|
||||||
|
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimModifier;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
|
||||||
|
import cz.muni.ics.oidc.server.claims.PerunCustomClaimDefinition;
|
||||||
|
import cz.muni.ics.oidc.server.userInfo.mappings.AddressMappings;
|
||||||
|
import cz.muni.ics.oidc.server.userInfo.mappings.EmailMappings;
|
||||||
|
import cz.muni.ics.oidc.server.userInfo.mappings.OpenidMappings;
|
||||||
|
import cz.muni.ics.oidc.server.userInfo.mappings.PhoneMappings;
|
||||||
|
import cz.muni.ics.oidc.server.userInfo.mappings.ProfileMappings;
|
||||||
|
import cz.muni.ics.openid.connect.model.Address;
|
||||||
|
import cz.muni.ics.openid.connect.model.DefaultAddress;
|
||||||
|
import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Slf4j
|
||||||
|
@Builder
|
||||||
|
public class PerunUserInfoCacheLoader extends CacheLoader<UserInfoCacheKey, UserInfo> {
|
||||||
|
|
||||||
|
private OpenidMappings openidMappings;
|
||||||
|
private ProfileMappings profileMappings;
|
||||||
|
private EmailMappings emailMappings;
|
||||||
|
private AddressMappings addressMappings;
|
||||||
|
private PhoneMappings phoneMappings;
|
||||||
|
private PerunAdapter perunAdapter;
|
||||||
|
private List<PerunCustomClaimDefinition> customClaims;
|
||||||
|
private boolean fillAttributes;
|
||||||
|
private List<ClaimModifier> subModifiers;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserInfo load(UserInfoCacheKey key) {
|
||||||
|
log.debug("load({}) ... populating cache for the key", key);
|
||||||
|
PerunUserInfo ui = new PerunUserInfo();
|
||||||
|
long perunUserId = key.getUserId();
|
||||||
|
Set<String> attributes = constructAttributes(key.getScopes());
|
||||||
|
Map<String, PerunAttributeValue> userAttributeValues = fetchUserAttributes(perunUserId, attributes);
|
||||||
|
|
||||||
|
ClaimSourceProduceContext.ClaimSourceProduceContextBuilder builder = ClaimSourceProduceContext.builder()
|
||||||
|
.perunUserId(perunUserId)
|
||||||
|
.sub(ui.getSub())
|
||||||
|
.attrValues(userAttributeValues)
|
||||||
|
.scopes(key.getScopes())
|
||||||
|
.client(key.getClient())
|
||||||
|
.perunAdapter(perunAdapter)
|
||||||
|
.samlAuthenticationDetails(key.getAuthenticationDetails());
|
||||||
|
if (key.getClient() != null) {
|
||||||
|
builder = builder.facility(perunAdapter.getFacilityByClientId(key.getClient().getClientId()));
|
||||||
|
}
|
||||||
|
ClaimSourceProduceContext pctx = builder.build();
|
||||||
|
|
||||||
|
processStandardScopes(pctx, ui);
|
||||||
|
processCustomScopes(pctx, ui);
|
||||||
|
return ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, PerunAttributeValue> fetchUserAttributes(long perunUserId, Set<String> attributes) {
|
||||||
|
Map<String, PerunAttributeValue> userAttributeValues =
|
||||||
|
perunAdapter.getUserAttributeValues(perunUserId, attributes);
|
||||||
|
|
||||||
|
if (shouldFillAttrs(userAttributeValues)) {
|
||||||
|
List<String> attrNames = userAttributeValues.entrySet()
|
||||||
|
.stream()
|
||||||
|
.filter(entry -> (null == entry.getValue() || entry.getValue().isNullValue()))
|
||||||
|
.map(Map.Entry::getKey)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Map<String, PerunAttributeValue> missingAttrs = perunAdapter.getAdapterFallback()
|
||||||
|
.getUserAttributeValues(perunUserId, attrNames);
|
||||||
|
userAttributeValues.putAll(missingAttrs);
|
||||||
|
}
|
||||||
|
return userAttributeValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> constructAttributes(Set<String> requestedScopes) {
|
||||||
|
Set<String> attributes = new HashSet<>();
|
||||||
|
if (requestedScopes != null && !requestedScopes.isEmpty()) {
|
||||||
|
if (requestedScopes.contains(OPENID)) {
|
||||||
|
attributes.addAll(openidMappings.getAttrNames());
|
||||||
|
}
|
||||||
|
if (requestedScopes.contains(PROFILE)) {
|
||||||
|
attributes.addAll(profileMappings.getAttrNames());
|
||||||
|
}
|
||||||
|
if (requestedScopes.contains(EMAIL)) {
|
||||||
|
attributes.addAll(emailMappings.getAttrNames());
|
||||||
|
}
|
||||||
|
if (requestedScopes.contains(ADDRESS)) {
|
||||||
|
attributes.addAll(addressMappings.getAttrNames());
|
||||||
|
}
|
||||||
|
if (requestedScopes.contains(PHONE)) {
|
||||||
|
attributes.addAll(phoneMappings.getAttrNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PerunCustomClaimDefinition pccd : customClaims) {
|
||||||
|
if (requestedScopes.contains(pccd.getScope())) {
|
||||||
|
attributes.addAll(pccd.getClaimSource().getAttrIdentifiers());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processCustomScopes(ClaimSourceProduceContext pctx, PerunUserInfo ui) {
|
||||||
|
log.debug("processing custom claims");
|
||||||
|
for (PerunCustomClaimDefinition claimDef : customClaims) {
|
||||||
|
if (isScopeRequested(claimDef.getScope(), pctx.getScopes())) {
|
||||||
|
processCustomScope(claimDef, pctx, ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("UserInfo created");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processCustomScope(PerunCustomClaimDefinition claimDef, ClaimSourceProduceContext pctx, PerunUserInfo ui) {
|
||||||
|
log.debug("producing value for custom claim {}", claimDef.getClaim());
|
||||||
|
JsonNode claimInJson = claimDef.getClaimSource().produceValue(pctx);
|
||||||
|
log.debug("produced value {}={}", claimDef.getClaim(), claimInJson);
|
||||||
|
if (claimInJson == null || claimInJson.isNull()) {
|
||||||
|
log.debug("claim {} is null", claimDef.getClaim());
|
||||||
|
return;
|
||||||
|
} else if (claimInJson.isTextual() && !StringUtils.hasText(claimInJson.asText())) {
|
||||||
|
log.debug("claim {} is a string and it is empty or null", claimDef.getClaim());
|
||||||
|
return;
|
||||||
|
} else if ((claimInJson.isArray() || claimInJson.isObject()) && claimInJson.size() == 0) {
|
||||||
|
log.debug("claim {} is an object or array and it is empty or null", claimDef.getClaim());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<ClaimModifier> claimModifiers = claimDef.getClaimModifiers();
|
||||||
|
if (claimModifiers != null && !claimModifiers.isEmpty()) {
|
||||||
|
claimInJson = modifyClaims(claimModifiers, claimInJson);
|
||||||
|
}
|
||||||
|
ui.getCustomClaims().put(claimDef.getClaim(), claimInJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isScopeRequested(String scope, Set<String> scopes) {
|
||||||
|
return scopes != null && scopes.contains(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processStandardScopes(ClaimSourceProduceContext ctx, PerunUserInfo ui) {
|
||||||
|
Set<String> scopes = ctx.getScopes();
|
||||||
|
if (scopes != null && !scopes.isEmpty()) {
|
||||||
|
if (scopes.contains(OPENID)) {
|
||||||
|
processOpenid(ctx.getAttrValues(), ctx.getPerunUserId(), ui);
|
||||||
|
}
|
||||||
|
if (scopes.contains(PROFILE)) {
|
||||||
|
processProfile(ctx.getAttrValues(), ui);
|
||||||
|
}
|
||||||
|
if (scopes.contains(EMAIL)) {
|
||||||
|
processEmail(ctx.getAttrValues(), ui);
|
||||||
|
}
|
||||||
|
if (scopes.contains(ADDRESS)) {
|
||||||
|
processAddress(ctx.getAttrValues(), ui);
|
||||||
|
}
|
||||||
|
if (scopes.contains(PHONE)) {
|
||||||
|
processPhone(ctx.getAttrValues(), ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processOpenid(Map<String, PerunAttributeValue> userAttributeValues, long perunUserId,
|
||||||
|
PerunUserInfo ui) {
|
||||||
|
JsonNode subJson = extractJsonValue(openidMappings.getSub(), userAttributeValues);
|
||||||
|
if (subJson != null && !subJson.isNull() && StringUtils.hasText(subJson.asText())) {
|
||||||
|
if (subModifiers != null) {
|
||||||
|
subJson = modifyClaims(subModifiers, subJson);
|
||||||
|
if (subJson.asText() == null || !StringUtils.hasText(subJson.asText())) {
|
||||||
|
throw new RuntimeException("Sub has no value after modification for username " + perunUserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.setSub(subJson.asText());
|
||||||
|
}
|
||||||
|
ui.setId(perunUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processProfile(Map<String, PerunAttributeValue> userAttributeValues, PerunUserInfo ui) {
|
||||||
|
ui.setPreferredUsername(extractStringValue(profileMappings.getPreferredUsername(), userAttributeValues));
|
||||||
|
ui.setGivenName(extractStringValue(profileMappings.getGivenName(), userAttributeValues));
|
||||||
|
ui.setFamilyName(extractStringValue(profileMappings.getFamilyName(), userAttributeValues));
|
||||||
|
ui.setMiddleName(extractStringValue(profileMappings.getMiddleName(), userAttributeValues));
|
||||||
|
ui.setName(extractStringValue(profileMappings.getName(), userAttributeValues));
|
||||||
|
ui.setNickname(extractStringValue(profileMappings.getNickname(), userAttributeValues));
|
||||||
|
ui.setProfile(extractStringValue(profileMappings.getProfile(), userAttributeValues));
|
||||||
|
ui.setPicture(extractStringValue(profileMappings.getPicture(), userAttributeValues));
|
||||||
|
ui.setWebsite(extractStringValue(profileMappings.getWebsite(), userAttributeValues));
|
||||||
|
ui.setZoneinfo(extractStringValue(profileMappings.getZoneinfo(), userAttributeValues));
|
||||||
|
ui.setGender(extractStringValue(profileMappings.getGender(), userAttributeValues));
|
||||||
|
ui.setBirthdate(extractStringValue(profileMappings.getBirthdate(), userAttributeValues));
|
||||||
|
ui.setLocale(extractStringValue(profileMappings.getLocale(), userAttributeValues));
|
||||||
|
ui.setUpdatedAt(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processEmail(Map<String, PerunAttributeValue> userAttributeValues, PerunUserInfo ui) {
|
||||||
|
ui.setEmail(extractStringValue(emailMappings.getEmail(), userAttributeValues));
|
||||||
|
ui.setEmailVerified(Boolean.parseBoolean(
|
||||||
|
extractStringValue(emailMappings.getEmailVerified(), userAttributeValues)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processAddress(Map<String, PerunAttributeValue> userAttributeValues, PerunUserInfo ui) {
|
||||||
|
Address address = null;
|
||||||
|
if (isAddressAvailable(userAttributeValues)) {
|
||||||
|
address = new DefaultAddress();
|
||||||
|
address.setFormatted(extractStringValue(addressMappings.getFormatted(), userAttributeValues));
|
||||||
|
address.setStreetAddress(extractStringValue(addressMappings.getStreetAddress(), userAttributeValues));
|
||||||
|
address.setLocality(extractStringValue(addressMappings.getLocality(), userAttributeValues));
|
||||||
|
address.setPostalCode(extractStringValue(addressMappings.getPostalCode(), userAttributeValues));
|
||||||
|
address.setCountry(extractStringValue(addressMappings.getCountry(), userAttributeValues));
|
||||||
|
}
|
||||||
|
ui.setAddress(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processPhone(Map<String, PerunAttributeValue> userAttributeValues, PerunUserInfo ui) {
|
||||||
|
ui.setPhoneNumber(extractStringValue(phoneMappings.getPhoneNumber(), userAttributeValues));
|
||||||
|
ui.setPhoneNumberVerified(Boolean.parseBoolean(
|
||||||
|
extractStringValue(phoneMappings.getPhoneNumber(), userAttributeValues)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAddressAvailable(Map<String, PerunAttributeValue> userAttributeValues) {
|
||||||
|
return hasNonNullValue(addressMappings.getFormatted(), userAttributeValues)
|
||||||
|
|| hasNonNullValue(addressMappings.getStreetAddress(), userAttributeValues)
|
||||||
|
|| hasNonNullValue(addressMappings.getLocality(), userAttributeValues)
|
||||||
|
|| hasNonNullValue(addressMappings.getPostalCode(), userAttributeValues)
|
||||||
|
|| hasNonNullValue(addressMappings.getCountry(), userAttributeValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasNonNullValue(String mapping, Map<String, PerunAttributeValue> valueMap) {
|
||||||
|
if (mapping == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PerunAttributeValue v = valueMap.getOrDefault(mapping, null);
|
||||||
|
return v != null && !v.isNullValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode extractJsonValue(String mapping, Map<String, PerunAttributeValue> valueMap) {
|
||||||
|
PerunAttributeValue v = extractValue(mapping, valueMap);
|
||||||
|
if (v != null) {
|
||||||
|
return v.valueAsJson();
|
||||||
|
}
|
||||||
|
return JsonNodeFactory.instance.nullNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractStringValue(String mapping, Map<String, PerunAttributeValue> valueMap) {
|
||||||
|
PerunAttributeValue v = extractValue(mapping, valueMap);
|
||||||
|
if (v != null) {
|
||||||
|
return v.valueAsString();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerunAttributeValue extractValue(String mapping, Map<String, PerunAttributeValue> valueMap) {
|
||||||
|
if (!StringUtils.hasText(mapping)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return valueMap.getOrDefault(mapping, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode modifyClaims(List<ClaimModifier> claimModifiers, JsonNode value) {
|
||||||
|
for (ClaimModifier modifier: claimModifiers) {
|
||||||
|
value = modifyClaim(modifier, value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode modifyClaim(ClaimModifier modifier, JsonNode orig) {
|
||||||
|
JsonNode claimInJson = orig.deepCopy();
|
||||||
|
if (claimInJson.isTextual()) {
|
||||||
|
return TextNode.valueOf(modifier.modify(claimInJson.asText()));
|
||||||
|
} else if (claimInJson.isArray()) {
|
||||||
|
ArrayNode arrayNode = (ArrayNode) claimInJson;
|
||||||
|
for (int i = 0; i < arrayNode.size(); i++) {
|
||||||
|
JsonNode item = arrayNode.get(i);
|
||||||
|
if (item.isTextual()) {
|
||||||
|
String original = item.asText();
|
||||||
|
String modified = modifier.modify(original);
|
||||||
|
arrayNode.set(i, TextNode.valueOf(modified));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arrayNode;
|
||||||
|
} else {
|
||||||
|
log.warn("Original value is neither string nor array of strings - cannot modify values");
|
||||||
|
return orig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldFillAttrs(Map<String, PerunAttributeValue> userAttributeValues) {
|
||||||
|
if (fillAttributes) {
|
||||||
|
if (userAttributeValues.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
} else if (userAttributeValues.containsValue(null)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return !userAttributeValues.values().stream()
|
||||||
|
.filter(PerunAttributeValueAwareModel::isNullValue)
|
||||||
|
.collect(Collectors.toSet())
|
||||||
|
.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,49 +1,35 @@
|
||||||
package cz.muni.ics.oidc.server.userInfo;
|
package cz.muni.ics.oidc.server.userInfo;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
|
||||||
import com.fasterxml.jackson.databind.node.TextNode;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
|
||||||
import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
|
import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
|
||||||
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
||||||
|
import cz.muni.ics.oauth2.model.SamlAuthenticationDetails;
|
||||||
|
import cz.muni.ics.oauth2.model.SavedUserAuthentication;
|
||||||
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
|
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
|
||||||
import cz.muni.ics.oidc.exceptions.ConfigurationException;
|
import cz.muni.ics.oidc.exceptions.ConfigurationException;
|
||||||
import cz.muni.ics.oidc.models.Facility;
|
|
||||||
import cz.muni.ics.oidc.models.PerunAttributeValue;
|
|
||||||
import cz.muni.ics.oidc.models.PerunAttributeValueAwareModel;
|
|
||||||
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
|
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
|
||||||
import cz.muni.ics.oidc.server.claims.ClaimContextCommonParameters;
|
|
||||||
import cz.muni.ics.oidc.server.claims.ClaimModifier;
|
import cz.muni.ics.oidc.server.claims.ClaimModifier;
|
||||||
import cz.muni.ics.oidc.server.claims.ClaimModifierInitContext;
|
|
||||||
import cz.muni.ics.oidc.server.claims.ClaimSource;
|
|
||||||
import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
|
|
||||||
import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
|
|
||||||
import cz.muni.ics.oidc.server.claims.PerunCustomClaimDefinition;
|
import cz.muni.ics.oidc.server.claims.PerunCustomClaimDefinition;
|
||||||
import cz.muni.ics.oidc.server.claims.modifiers.NoOperationModifier;
|
|
||||||
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
|
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
|
||||||
import cz.muni.ics.openid.connect.model.Address;
|
import cz.muni.ics.oidc.server.userInfo.mappings.AddressMappings;
|
||||||
import cz.muni.ics.openid.connect.model.DefaultAddress;
|
import cz.muni.ics.oidc.server.userInfo.mappings.EmailMappings;
|
||||||
|
import cz.muni.ics.oidc.server.userInfo.mappings.OpenidMappings;
|
||||||
|
import cz.muni.ics.oidc.server.userInfo.mappings.PhoneMappings;
|
||||||
|
import cz.muni.ics.oidc.server.userInfo.mappings.ProfileMappings;
|
||||||
|
import cz.muni.ics.oidc.server.userInfo.modifiers.UserInfoModifierContext;
|
||||||
import cz.muni.ics.openid.connect.model.UserInfo;
|
import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
import cz.muni.ics.openid.connect.service.UserInfoService;
|
import cz.muni.ics.openid.connect.service.UserInfoService;
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.saml.SAMLCredential;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,12 +40,6 @@ import org.springframework.util.StringUtils;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class PerunUserInfoService implements UserInfoService {
|
public class PerunUserInfoService implements UserInfoService {
|
||||||
|
|
||||||
private static final String CUSTOM_CLAIM = "custom.claim.";
|
|
||||||
private static final String SOURCE = ".source";
|
|
||||||
private static final String CLASS = ".class";
|
|
||||||
private static final String NAMES = ".names";
|
|
||||||
private static final String MODIFIER = ".modifier";
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ClientDetailsEntityService clientService;
|
private ClientDetailsEntityService clientService;
|
||||||
|
|
||||||
|
@ -69,128 +49,44 @@ public class PerunUserInfoService implements UserInfoService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private PerunOidcConfig perunOidcConfig;
|
private PerunOidcConfig perunOidcConfig;
|
||||||
|
|
||||||
private List<String> forceRegenerateUserinfoCustomClaims = new ArrayList<>();
|
@Autowired
|
||||||
private List<String> forceRegenerateUserinfoStandardClaims = new ArrayList<>();
|
private OpenidMappings openidMappings;
|
||||||
|
|
||||||
private final LoadingCache<UserClientPair, UserInfo> cache;
|
@Autowired
|
||||||
|
private ProfileMappings profileMappings;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmailMappings emailMappings;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AddressMappings addressMappings;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PhoneMappings phoneMappings;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
private PerunAdapter perunAdapter;
|
private PerunAdapter perunAdapter;
|
||||||
|
|
||||||
|
private LoadingCache<UserInfoCacheKey, UserInfo> cache;
|
||||||
|
|
||||||
private Properties properties;
|
private Properties properties;
|
||||||
|
|
||||||
private String subAttribute;
|
|
||||||
private List<ClaimModifier> subModifiers;
|
|
||||||
private String preferredUsernameAttribute;
|
|
||||||
private String givenNameAttribute;
|
|
||||||
private String familyNameAttribute;
|
|
||||||
private String middleNameAttribute;
|
|
||||||
private String fullNameAttribute;
|
|
||||||
private String emailAttribute;
|
|
||||||
private String addressAttribute;
|
|
||||||
private String phoneAttribute;
|
|
||||||
private String zoneinfoAttribute;
|
|
||||||
private String localeAttribute;
|
|
||||||
private Set<String> customClaimNames;
|
private Set<String> customClaimNames;
|
||||||
|
|
||||||
private List<PerunCustomClaimDefinition> customClaims = new ArrayList<>();
|
private List<PerunCustomClaimDefinition> customClaims = new ArrayList<>();
|
||||||
|
|
||||||
private UserInfoModifierContext userInfoModifierContext;
|
private UserInfoModifierContext userInfoModifierContext;
|
||||||
|
|
||||||
private final Set<String> userAttrNames = new HashSet<>();
|
private List<String> forceRegenerateUserinfoCustomClaims = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<String> forceRegenerateUserinfoStandardClaims = new ArrayList<>();
|
||||||
|
|
||||||
|
// == setters and getters ==
|
||||||
|
|
||||||
public void setProperties(Properties properties) {
|
public void setProperties(Properties properties) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPerunAdapter(PerunAdapter perunAdapter) {
|
|
||||||
this.perunAdapter = perunAdapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubAttribute(String subAttribute) {
|
|
||||||
if (this.subAttribute != null) {
|
|
||||||
userAttrNames.remove(this.subAttribute);
|
|
||||||
}
|
|
||||||
userAttrNames.add(subAttribute);
|
|
||||||
this.subAttribute = subAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPreferredUsernameAttribute(String preferredUsernameAttribute) {
|
|
||||||
if (this.preferredUsernameAttribute != null) {
|
|
||||||
userAttrNames.remove(this.preferredUsernameAttribute);
|
|
||||||
}
|
|
||||||
userAttrNames.add(preferredUsernameAttribute);
|
|
||||||
this.preferredUsernameAttribute = preferredUsernameAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGivenNameAttribute(String givenNameAttribute) {
|
|
||||||
if (this.givenNameAttribute != null) {
|
|
||||||
userAttrNames.remove(this.givenNameAttribute);
|
|
||||||
}
|
|
||||||
userAttrNames.add(givenNameAttribute);
|
|
||||||
this.givenNameAttribute = givenNameAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFamilyNameAttribute(String familyNameAttribute) {
|
|
||||||
if (this.familyNameAttribute != null) {
|
|
||||||
userAttrNames.remove(this.familyNameAttribute);
|
|
||||||
}
|
|
||||||
userAttrNames.add(familyNameAttribute);
|
|
||||||
this.familyNameAttribute = familyNameAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMiddleNameAttribute(String middleNameAttribute) {
|
|
||||||
if (this.middleNameAttribute != null) {
|
|
||||||
userAttrNames.remove(this.middleNameAttribute);
|
|
||||||
}
|
|
||||||
userAttrNames.add(middleNameAttribute);
|
|
||||||
this.middleNameAttribute = middleNameAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFullNameAttribute(String fullNameAttribute) {
|
|
||||||
if (this.fullNameAttribute != null) {
|
|
||||||
userAttrNames.remove(this.fullNameAttribute);
|
|
||||||
}
|
|
||||||
userAttrNames.add(fullNameAttribute);
|
|
||||||
this.fullNameAttribute = fullNameAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmailAttribute(String emailAttribute) {
|
|
||||||
if (this.emailAttribute != null) {
|
|
||||||
userAttrNames.remove(this.emailAttribute);
|
|
||||||
}
|
|
||||||
userAttrNames.add(emailAttribute);
|
|
||||||
this.emailAttribute = emailAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAddressAttribute(String addressAttribute) {
|
|
||||||
if (this.addressAttribute != null) {
|
|
||||||
userAttrNames.remove(this.addressAttribute);
|
|
||||||
}
|
|
||||||
userAttrNames.add(addressAttribute);
|
|
||||||
this.addressAttribute = addressAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPhoneAttribute(String phoneAttribute) {
|
|
||||||
if (this.phoneAttribute != null) {
|
|
||||||
userAttrNames.remove(this.phoneAttribute);
|
|
||||||
}
|
|
||||||
userAttrNames.add(phoneAttribute);
|
|
||||||
this.phoneAttribute = phoneAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setZoneinfoAttribute(String zoneinfoAttribute) {
|
|
||||||
if (this.zoneinfoAttribute != null) {
|
|
||||||
userAttrNames.remove(this.zoneinfoAttribute);
|
|
||||||
}
|
|
||||||
userAttrNames.add(zoneinfoAttribute);
|
|
||||||
this.zoneinfoAttribute = zoneinfoAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLocaleAttribute(String localeAttribute) {
|
|
||||||
if (this.localeAttribute != null) {
|
|
||||||
userAttrNames.remove(this.localeAttribute);
|
|
||||||
}
|
|
||||||
userAttrNames.add(localeAttribute);
|
|
||||||
this.localeAttribute = localeAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomClaimNames(Set<String> customClaimNames) {
|
public void setCustomClaimNames(Set<String> customClaimNames) {
|
||||||
this.customClaimNames = customClaimNames;
|
this.customClaimNames = customClaimNames;
|
||||||
}
|
}
|
||||||
|
@ -207,52 +103,84 @@ public class PerunUserInfoService implements UserInfoService {
|
||||||
return customClaims;
|
return customClaims;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPerunAdapter(PerunAdapter perunAdapter) {
|
||||||
|
this.perunAdapter = perunAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// == init ==
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void postInit() throws ConfigurationException {
|
public void postInit() throws ConfigurationException {
|
||||||
log.debug("trying to load modifier for attribute.openid.sub");
|
|
||||||
subModifiers = loadClaimValueModifiers("sub", "attribute.openid.sub" + MODIFIER);
|
|
||||||
//custom claims
|
//custom claims
|
||||||
this.customClaims = new ArrayList<>(customClaimNames.size());
|
this.customClaims = UserInfoUtils.loadCustomClaims(customClaimNames, properties, perunOidcConfig, jwtService);
|
||||||
for (String claimName : customClaimNames) {
|
|
||||||
String propertyBase = CUSTOM_CLAIM + claimName;
|
|
||||||
//get scope
|
|
||||||
String scopeProperty = propertyBase + ".scope";
|
|
||||||
String scope = properties.getProperty(scopeProperty);
|
|
||||||
if (scope == null) {
|
|
||||||
log.error("property {} not found, skipping custom claim {}", scopeProperty, claimName);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
//get ClaimSource
|
|
||||||
ClaimSource claimSource = loadClaimSource(claimName, propertyBase + SOURCE);
|
|
||||||
userAttrNames.addAll(claimSource.getAttrIdentifiers());
|
|
||||||
//optional claim value modifier
|
|
||||||
List<ClaimModifier> claimModifiers = loadClaimValueModifiers(claimName, propertyBase + MODIFIER);
|
|
||||||
//add claim definition
|
|
||||||
customClaims.add(new PerunCustomClaimDefinition(scope, claimName, claimSource, claimModifiers));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
this.userInfoModifierContext = new UserInfoModifierContext(properties, perunAdapter);
|
this.userInfoModifierContext = new UserInfoModifierContext(properties, perunAdapter);
|
||||||
|
|
||||||
|
log.debug("trying to load modifier for attribute.openid.sub");
|
||||||
|
List<ClaimModifier> subModifiers = UserInfoUtils.loadClaimValueModifiers(
|
||||||
|
properties, "sub", "attribute.openid.sub");
|
||||||
|
|
||||||
|
PerunUserInfoCacheLoader cacheLoader = PerunUserInfoCacheLoader.builder()
|
||||||
|
.openidMappings(openidMappings)
|
||||||
|
.profileMappings(profileMappings)
|
||||||
|
.emailMappings(emailMappings)
|
||||||
|
.phoneMappings(phoneMappings)
|
||||||
|
.addressMappings(addressMappings)
|
||||||
|
.customClaims(customClaims)
|
||||||
|
.fillAttributes(perunOidcConfig.isFillMissingUserAttrs())
|
||||||
|
.perunAdapter(perunAdapter)
|
||||||
|
.subModifiers(subModifiers)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.cache = CacheBuilder.newBuilder()
|
||||||
|
.maximumSize(100)
|
||||||
|
.expireAfterAccess(java.time.Duration.ofSeconds(60))
|
||||||
|
.expireAfterWrite(java.time.Duration.ofSeconds(300))
|
||||||
|
.build(cacheLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// == public methods ==
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserInfo get(String username, String clientId, Set<String> scope, SavedUserAuthentication userAuthentication) {
|
||||||
|
return get(username, clientId, scope, userAuthentication.getAuthenticationDetails());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserInfo getByUsernameAndClientId(String username, String clientId) {
|
public UserInfo get(String username, String clientId, Set<String> scope, SAMLCredential samlCredential) {
|
||||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
return get(username, clientId, scope, new SamlAuthenticationDetails(samlCredential));
|
||||||
if (client == null) {
|
}
|
||||||
log.warn("did not found client with id {}", clientId);
|
|
||||||
|
@Override
|
||||||
|
public UserInfo get(String username, String clientId, Set<String> scope) {
|
||||||
|
return get(username, clientId, scope, new SamlAuthenticationDetails());
|
||||||
|
}
|
||||||
|
|
||||||
|
// == private methods ==
|
||||||
|
|
||||||
|
private UserInfo get(String username, String clientId, Set<String> scope, SamlAuthenticationDetails details) {
|
||||||
|
if (!StringUtils.hasText(clientId)) {
|
||||||
|
log.warn("No client_id provided, cannot get userinfo");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
ClientDetailsEntity client = null;
|
||||||
|
if (StringUtils.hasText(clientId)) {
|
||||||
|
client = clientService.loadClientByClientId(clientId);
|
||||||
|
if (client == null) {
|
||||||
|
log.warn("Did not find client with id '{}', cannot get userinfo", clientId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PerunUserInfo userInfo;
|
PerunUserInfo userInfo;
|
||||||
try {
|
try {
|
||||||
UserClientPair cacheKey = new UserClientPair(username, clientId, client);
|
UserInfoCacheKey cacheKey = new UserInfoCacheKey(username, client, details, scope);
|
||||||
userInfo = (PerunUserInfo) cache.get(cacheKey);
|
userInfo = (PerunUserInfo) cache.get(cacheKey);
|
||||||
if (!checkStandardClaims(userInfo) || !checkCustomClaims(userInfo)) {
|
if (!checkStandardClaims(userInfo) || !checkCustomClaims(userInfo)) {
|
||||||
log.info("Some required claim is null, regenerate userInfo");
|
log.info("Some required claim is null, regenerate userInfo");
|
||||||
cache.invalidate(cacheKey);
|
cache.invalidate(cacheKey);
|
||||||
userInfo = (PerunUserInfo) cache.get(cacheKey);
|
userInfo = (PerunUserInfo) cache.get(cacheKey);
|
||||||
}
|
}
|
||||||
log.debug("loaded UserInfo from cache for '{}'/'{}'", userInfo.getName(), client.getClientName());
|
|
||||||
userInfo = userInfoModifierContext.modify(userInfo, clientId);
|
userInfo = userInfoModifierContext.modify(userInfo, clientId);
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
log.error("cannot get user from cache", e);
|
log.error("cannot get user from cache", e);
|
||||||
|
@ -262,330 +190,6 @@ public class PerunUserInfoService implements UserInfoService {
|
||||||
return userInfo;
|
return userInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserInfo getByUsername(String username) {
|
|
||||||
PerunUserInfo userInfo;
|
|
||||||
try {
|
|
||||||
UserClientPair cacheKey = new UserClientPair(username);
|
|
||||||
userInfo = (PerunUserInfo) cache.get(cacheKey);
|
|
||||||
if (!checkStandardClaims(userInfo) || !checkCustomClaims(userInfo)) {
|
|
||||||
log.info("Some required claim is null, regenerate userInfo");
|
|
||||||
cache.invalidate(cacheKey);
|
|
||||||
userInfo = (PerunUserInfo) cache.get(cacheKey);
|
|
||||||
}
|
|
||||||
log.debug("loaded UserInfo from cache for '{}'", userInfo.getName());
|
|
||||||
userInfo = userInfoModifierContext.modify(userInfo, null);
|
|
||||||
} catch (UncheckedExecutionException | ExecutionException e) {
|
|
||||||
log.error("cannot get user from cache", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return userInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserInfo getByEmailAddress(String email) {
|
|
||||||
throw new RuntimeException("PerunUserInfoService.getByEmailAddress() not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
public PerunUserInfoService() {
|
|
||||||
this.cache = CacheBuilder.newBuilder()
|
|
||||||
.maximumSize(100)
|
|
||||||
.expireAfterAccess(60, TimeUnit.SECONDS)
|
|
||||||
.build(cacheLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ClaimModifier> loadClaimValueModifiers(String claimName, String propertyPrefix)
|
|
||||||
throws ConfigurationException
|
|
||||||
{
|
|
||||||
String names = properties.getProperty(propertyPrefix + NAMES, "");
|
|
||||||
String[] nameArr = names.split(",");
|
|
||||||
List<ClaimModifier> modifiers = new ArrayList<>();
|
|
||||||
if (nameArr.length > 0) {
|
|
||||||
for (String name : nameArr) {
|
|
||||||
modifiers.add(loadClaimValueModifier(claimName, propertyPrefix + '.' + name, name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modifiers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClaimModifier loadClaimValueModifier(String claimName, String propertyPrefix, String modifierName)
|
|
||||||
throws ConfigurationException
|
|
||||||
{
|
|
||||||
String modifierClass = properties.getProperty(propertyPrefix + CLASS, NoOperationModifier.class.getName());
|
|
||||||
if (!StringUtils.hasText(modifierClass)) {
|
|
||||||
log.debug("{}:{} - no class has ben configured for claim value modifier, use noop modifier",
|
|
||||||
claimName, modifierName);
|
|
||||||
modifierClass = NoOperationModifier.class.getName();
|
|
||||||
}
|
|
||||||
log.trace("{}:{} - loading ClaimModifier class '{}'", claimName, modifierName, modifierClass);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Class<?> rawClazz = Class.forName(modifierClass);
|
|
||||||
if (!ClaimModifier.class.isAssignableFrom(rawClazz)) {
|
|
||||||
log.error("{}:{} - failed to initialized claim modifier: class '{}' does not extend ClaimModifier",
|
|
||||||
claimName, modifierName, modifierClass);
|
|
||||||
throw new ConfigurationException("No instantiable class modifier configured for claim " + claimName);
|
|
||||||
}
|
|
||||||
@SuppressWarnings("unchecked") Class<ClaimModifier> clazz = (Class<ClaimModifier>) rawClazz;
|
|
||||||
Constructor<ClaimModifier> constructor = clazz.getConstructor(ClaimModifierInitContext.class);
|
|
||||||
ClaimModifierInitContext ctx = new ClaimModifierInitContext(
|
|
||||||
propertyPrefix, properties, claimName, modifierName);
|
|
||||||
return constructor.newInstance(ctx);
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
log.error("{}:{} - failed to initialize claim modifier: class '{}' was not found",
|
|
||||||
claimName, modifierName, modifierClass);
|
|
||||||
log.trace("{}:{} - details:", claimName, modifierName, e);
|
|
||||||
throw new ConfigurationException("Error has occurred when instantiating claim modifier '"
|
|
||||||
+ modifierName + "' of claim '" + claimName + '\'');
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
log.error("{}:{} - failed to initialize claim modifier: class '{}' does not have proper constructor",
|
|
||||||
claimName, modifierName, modifierClass);
|
|
||||||
log.trace("{}:{} - details:", claimName, e);
|
|
||||||
throw new ConfigurationException("Error has occurred when instantiating claim modifier '"
|
|
||||||
+ modifierName + "' of claim '" + claimName + '\'');
|
|
||||||
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
|
|
||||||
log.error("{}:{} - failed to initialize claim modifier: class '{}' cannot be instantiated",
|
|
||||||
claimName, modifierName, modifierClass);
|
|
||||||
log.trace("{}:{} - details:", claimName, e);
|
|
||||||
throw new ConfigurationException("Error has occurred when instantiating claim modifier '"
|
|
||||||
+ modifierName + "' of claim '" + claimName + '\'');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClaimSource loadClaimSource(String claimName, String propertyPrefix) throws ConfigurationException {
|
|
||||||
String sourceClass = properties.getProperty(propertyPrefix + CLASS);
|
|
||||||
if (!StringUtils.hasText(sourceClass)) {
|
|
||||||
log.error("{} - failed to initialized claim source: no class has ben configured", claimName);
|
|
||||||
throw new ConfigurationException("No class configured for claim source");
|
|
||||||
}
|
|
||||||
|
|
||||||
log.trace("{} - loading ClaimSource class '{}'", claimName, sourceClass);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Class<?> rawClazz = Class.forName(sourceClass);
|
|
||||||
if (!ClaimSource.class.isAssignableFrom(rawClazz)) {
|
|
||||||
log.error("{} - failed to initialized claim source: class '{}' does not extend ClaimSource",
|
|
||||||
claimName, sourceClass);
|
|
||||||
throw new ConfigurationException("No instantiable class source configured for claim " + claimName);
|
|
||||||
}
|
|
||||||
@SuppressWarnings("unchecked") Class<ClaimSource> clazz = (Class<ClaimSource>) rawClazz;
|
|
||||||
Constructor<ClaimSource> constructor = clazz.getConstructor(ClaimSourceInitContext.class);
|
|
||||||
ClaimSourceInitContext ctx = new ClaimSourceInitContext(perunOidcConfig, jwtService, propertyPrefix,
|
|
||||||
properties, claimName);
|
|
||||||
return constructor.newInstance(ctx);
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
log.error("{} - failed to initialize claim source: class '{}' was not found", claimName, sourceClass);
|
|
||||||
log.trace("{} - details:", claimName, e);
|
|
||||||
throw new ConfigurationException("Error has occurred when instantiating claim source for claim " + claimName);
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
log.error("{} - failed to initialize claim source: class '{}' does not have proper constructor",
|
|
||||||
claimName, sourceClass);
|
|
||||||
log.trace("{} - details:", claimName, e);
|
|
||||||
throw new ConfigurationException("Error has occurred when instantiating claim source for claim " + claimName);
|
|
||||||
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
|
|
||||||
log.error("{} - failed to initialize claim source: class '{}' cannot be instantiated", claimName, sourceClass);
|
|
||||||
log.trace("{} - details:", claimName, e);
|
|
||||||
throw new ConfigurationException("Error has occurred when instantiating claim source for claim " + claimName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class UserClientPair {
|
|
||||||
private final long userId;
|
|
||||||
private String clientId;
|
|
||||||
private ClientDetailsEntity client;
|
|
||||||
|
|
||||||
UserClientPair(String userId) {
|
|
||||||
this.userId = Long.parseLong(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserClientPair(String userId, String clientId, ClientDetailsEntity client) {
|
|
||||||
this.userId = Long.parseLong(userId);
|
|
||||||
this.clientId = clientId;
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getUserId() {
|
|
||||||
return userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClientId() {
|
|
||||||
return clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientDetailsEntity getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
UserClientPair that = (UserClientPair) o;
|
|
||||||
return userId == that.userId &&
|
|
||||||
Objects.equals(clientId, that.clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(userId, clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "(" + "userId=" + userId + "," + (client == null ? "null" : "client=" + clientId + " '" + client.getClientName()) + "')";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
|
||||||
private final CacheLoader<UserClientPair, UserInfo> cacheLoader = new CacheLoader<UserClientPair, UserInfo>() {
|
|
||||||
@Override
|
|
||||||
public UserInfo load(UserClientPair pair) {
|
|
||||||
log.debug("load({}) ... populating cache for the key", pair);
|
|
||||||
PerunUserInfo ui = new PerunUserInfo();
|
|
||||||
long perunUserId = pair.getUserId();
|
|
||||||
|
|
||||||
Map<String, PerunAttributeValue> userAttributeValues = perunAdapter.getUserAttributeValues(perunUserId, userAttrNames);
|
|
||||||
|
|
||||||
if (shouldFillAttrs(userAttributeValues)) {
|
|
||||||
List<String> attrNames = userAttributeValues.entrySet()
|
|
||||||
.stream()
|
|
||||||
.filter(entry -> (null == entry.getValue() || entry.getValue().isNullValue()))
|
|
||||||
.map(Map.Entry::getKey)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
Map<String, PerunAttributeValue> missingAttrs = perunAdapter.getAdapterFallback()
|
|
||||||
.getUserAttributeValues(perunUserId, attrNames);
|
|
||||||
for (Map.Entry<String, PerunAttributeValue> entry : missingAttrs.entrySet()) {
|
|
||||||
userAttributeValues.put(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonNode subJson = userAttributeValues.get(subAttribute).valueAsJson();
|
|
||||||
if (subJson == null || subJson.isNull() || !StringUtils.hasText(subJson.asText())) {
|
|
||||||
throw new RuntimeException("cannot get sub from attribute " + subAttribute + " for username " + perunUserId);
|
|
||||||
}
|
|
||||||
String sub = subJson.asText();
|
|
||||||
if (subModifiers != null) {
|
|
||||||
subJson = modifyClaims(subModifiers, subJson);
|
|
||||||
sub = subJson.asText();
|
|
||||||
if (sub == null || !StringUtils.hasText(sub)) {
|
|
||||||
throw new RuntimeException("Sub has no value after modification for username " + perunUserId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.setId(perunUserId);
|
|
||||||
ui.setSub(sub); // Subject - Identifier for the End-User at the Issuer.
|
|
||||||
|
|
||||||
ui.setPreferredUsername(userAttributeValues.get(preferredUsernameAttribute).valueAsString()); // Shorthand name by which the End-User wishes to be referred to at the RP
|
|
||||||
ui.setGivenName(userAttributeValues.get(givenNameAttribute).valueAsString()); // Given name(s) or first name(s) of the End-User
|
|
||||||
ui.setFamilyName(userAttributeValues.get(familyNameAttribute).valueAsString()); // Surname(s) or last name(s) of the End-User
|
|
||||||
ui.setMiddleName(userAttributeValues.get(middleNameAttribute).valueAsString()); // Middle name(s) of the End-User
|
|
||||||
ui.setName(userAttributeValues.get(fullNameAttribute).valueAsString()); // End-User's full name
|
|
||||||
//ui.setNickname(); // Casual name of the End-User
|
|
||||||
//ui.setProfile(); // URL of the End-User's profile page.
|
|
||||||
//ui.setPicture(); // URL of the End-User's profile picture.
|
|
||||||
//ui.setWebsite(); // URL of the End-User's Web page or blog.
|
|
||||||
ui.setEmail(userAttributeValues.get(emailAttribute).valueAsString()); // End-User's preferred e-mail address.
|
|
||||||
//ui.setEmailVerified(true); // True if the End-User's e-mail address has been verified
|
|
||||||
//ui.setGender("male"); // End-User's gender. Values defined by this specification are female and male.
|
|
||||||
//ui.setBirthdate("1975-01-01");//End-User's birthday, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format.
|
|
||||||
ui.setZoneinfo(userAttributeValues.get(zoneinfoAttribute).valueAsString());//String from zoneinfo [zoneinfo] time zone database, For example, Europe/Paris
|
|
||||||
ui.setLocale(userAttributeValues.get(localeAttribute).valueAsString()); // For example, en-US or fr-CA.
|
|
||||||
ui.setPhoneNumber(userAttributeValues.get(phoneAttribute).valueAsString()); //[E.164] is RECOMMENDED as the format, for example, +1 (425) 555-121
|
|
||||||
//ui.setPhoneNumberVerified(true); // True if the End-User's phone number has been verified
|
|
||||||
//ui.setUpdatedTime(Long.toString(System.currentTimeMillis()/1000L));// value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time
|
|
||||||
Address address = null;
|
|
||||||
if (StringUtils.hasText(userAttributeValues.get(addressAttribute).valueAsString())) {
|
|
||||||
address = new DefaultAddress();
|
|
||||||
address.setFormatted(userAttributeValues.get(addressAttribute).valueAsString());
|
|
||||||
//address.setStreetAddress("Šumavská 15");
|
|
||||||
//address.setLocality("Brno");
|
|
||||||
//address.setPostalCode("61200");
|
|
||||||
//address.setCountry("Czech Republic");
|
|
||||||
}
|
|
||||||
ui.setAddress(address);
|
|
||||||
//custom claims
|
|
||||||
ClaimContextCommonParameters contextCommonParameters = getClaimContextCommonParameters(perunUserId,
|
|
||||||
pair.getClientId(), perunAdapter);
|
|
||||||
ClaimSourceProduceContext pctx = new ClaimSourceProduceContext(perunUserId, sub, userAttributeValues,
|
|
||||||
perunAdapter, pair.getClient(), contextCommonParameters);
|
|
||||||
log.debug("processing custom claims");
|
|
||||||
for (PerunCustomClaimDefinition pccd : customClaims) {
|
|
||||||
log.debug("producing value for custom claim {}", pccd.getClaim());
|
|
||||||
JsonNode claimInJson = pccd.getClaimSource().produceValue(pctx);
|
|
||||||
log.debug("produced value {}={}", pccd.getClaim(), claimInJson);
|
|
||||||
if (claimInJson == null || claimInJson.isNull()) {
|
|
||||||
log.debug("claim {} is null", pccd.getClaim());
|
|
||||||
continue;
|
|
||||||
} else if (claimInJson.isTextual() && !StringUtils.hasText(claimInJson.asText())) {
|
|
||||||
log.debug("claim {} is a string and it is empty or null", pccd.getClaim());
|
|
||||||
continue;
|
|
||||||
} else if ((claimInJson.isArray() || claimInJson.isObject()) && claimInJson.size() == 0) {
|
|
||||||
log.debug("claim {} is an object or array and it is empty or null", pccd.getClaim());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
List<ClaimModifier> claimModifiers = pccd.getClaimModifiers();
|
|
||||||
if (claimModifiers != null && !claimModifiers.isEmpty()) {
|
|
||||||
claimInJson = modifyClaims(claimModifiers, claimInJson);
|
|
||||||
}
|
|
||||||
ui.getCustomClaims().put(pccd.getClaim(), claimInJson);
|
|
||||||
}
|
|
||||||
log.debug("UserInfo created");
|
|
||||||
return ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClaimContextCommonParameters getClaimContextCommonParameters(long perunUserId, String clientId,
|
|
||||||
PerunAdapter perunAdapter)
|
|
||||||
{
|
|
||||||
Facility facility = perunAdapter.getFacilityByClientId(clientId);
|
|
||||||
return new ClaimContextCommonParameters(facility);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private JsonNode modifyClaims(List<ClaimModifier> claimModifiers, JsonNode value) {
|
|
||||||
for (ClaimModifier modifier: claimModifiers) {
|
|
||||||
value = modifyClaim(modifier, value);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonNode modifyClaim(ClaimModifier modifier, JsonNode orig) {
|
|
||||||
JsonNode claimInJson = orig.deepCopy();
|
|
||||||
if (claimInJson.isTextual()) {
|
|
||||||
return TextNode.valueOf(modifier.modify(claimInJson.asText()));
|
|
||||||
} else if (claimInJson.isArray()) {
|
|
||||||
ArrayNode arrayNode = (ArrayNode) claimInJson;
|
|
||||||
for (int i = 0; i < arrayNode.size(); i++) {
|
|
||||||
JsonNode item = arrayNode.get(i);
|
|
||||||
if (item.isTextual()) {
|
|
||||||
String original = item.asText();
|
|
||||||
String modified = modifier.modify(original);
|
|
||||||
arrayNode.set(i, TextNode.valueOf(modified));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arrayNode;
|
|
||||||
} else {
|
|
||||||
log.warn("Original value is neither string nor array of strings - cannot modify values");
|
|
||||||
return orig;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldFillAttrs(Map<String, PerunAttributeValue> userAttributeValues) {
|
|
||||||
if (perunOidcConfig.isFillMissingUserAttrs()) {
|
|
||||||
if (userAttributeValues.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
} else if (userAttributeValues.containsValue(null)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return !userAttributeValues.values().stream()
|
|
||||||
.filter(PerunAttributeValueAwareModel::isNullValue)
|
|
||||||
.collect(Collectors.toSet())
|
|
||||||
.isEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkStandardClaims(PerunUserInfo userInfo) {
|
private boolean checkStandardClaims(PerunUserInfo userInfo) {
|
||||||
for (String claim: forceRegenerateUserinfoStandardClaims) {
|
for (String claim: forceRegenerateUserinfoStandardClaims) {
|
||||||
switch (claim.toLowerCase()) {
|
switch (claim.toLowerCase()) {
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package cz.muni.ics.oidc.server.userInfo;
|
||||||
|
|
||||||
|
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
||||||
|
import cz.muni.ics.oauth2.model.SamlAuthenticationDetails;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class UserInfoCacheKey {
|
||||||
|
|
||||||
|
private final long userId;
|
||||||
|
private final ClientDetailsEntity client;
|
||||||
|
private final SamlAuthenticationDetails authenticationDetails;
|
||||||
|
private final Set<String> scopes;
|
||||||
|
|
||||||
|
public UserInfoCacheKey(String userId,
|
||||||
|
ClientDetailsEntity client,
|
||||||
|
SamlAuthenticationDetails authenticationDetails,
|
||||||
|
Set<String> scopes)
|
||||||
|
{
|
||||||
|
this.userId = Long.parseLong(userId);
|
||||||
|
this.client = client;
|
||||||
|
this.authenticationDetails = authenticationDetails;
|
||||||
|
this.scopes = scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
UserInfoCacheKey that = (UserInfoCacheKey) o;
|
||||||
|
String ourClientId = client != null ? client.getClientId() : null;
|
||||||
|
String theirClientId = that.client != null ? that.client.getClientId() : null;
|
||||||
|
return userId == that.userId
|
||||||
|
&& Objects.equals(ourClientId, theirClientId)
|
||||||
|
&& Objects.equals(authenticationDetails, that.authenticationDetails)
|
||||||
|
&& Objects.equals(scopes, that.scopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
String clientId = client != null ? client.getClientId() : null;
|
||||||
|
if (clientId != null) {
|
||||||
|
return Objects.hash(userId, clientId, authenticationDetails, scopes);
|
||||||
|
} else {
|
||||||
|
return Objects.hash(userId, authenticationDetails, scopes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
package cz.muni.ics.oidc.server.userInfo;
|
||||||
|
|
||||||
|
import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
|
||||||
|
import cz.muni.ics.oidc.exceptions.ConfigurationException;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimModifier;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimModifierInitContext;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimSource;
|
||||||
|
import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
|
||||||
|
import cz.muni.ics.oidc.server.claims.PerunCustomClaimDefinition;
|
||||||
|
import cz.muni.ics.oidc.server.claims.modifiers.NoOperationModifier;
|
||||||
|
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class UserInfoUtils {
|
||||||
|
|
||||||
|
public static final String CUSTOM_CLAIM = "custom.claim.";
|
||||||
|
public static final String SOURCE = ".source";
|
||||||
|
public static final String CLASS = ".class";
|
||||||
|
public static final String NAMES = ".names";
|
||||||
|
public static final String MODIFIER = ".modifier";
|
||||||
|
public static final String SCOPE = ".scope";
|
||||||
|
|
||||||
|
public static List<PerunCustomClaimDefinition> loadCustomClaims(Collection<String> customClaimNames,
|
||||||
|
Properties properties,
|
||||||
|
PerunOidcConfig oidcConfig,
|
||||||
|
JWTSigningAndValidationService jwtService)
|
||||||
|
throws ConfigurationException
|
||||||
|
{
|
||||||
|
List<PerunCustomClaimDefinition> customClaims = new ArrayList<>();
|
||||||
|
if (customClaimNames == null || customClaimNames.isEmpty()) {
|
||||||
|
return customClaims;
|
||||||
|
}
|
||||||
|
for (String claimName : customClaimNames) {
|
||||||
|
String propertyBase = UserInfoUtils.CUSTOM_CLAIM + claimName;
|
||||||
|
//get scope
|
||||||
|
String scopeProperty = propertyBase + UserInfoUtils.SCOPE;
|
||||||
|
String scope = properties.getProperty(scopeProperty);
|
||||||
|
if (scope == null) {
|
||||||
|
log.error("property {} not found, skipping custom claim {}", scopeProperty, claimName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//get ClaimSource
|
||||||
|
ClaimSource claimSource = UserInfoUtils.loadClaimSource(
|
||||||
|
properties, oidcConfig, jwtService, claimName, propertyBase);
|
||||||
|
//optional claim value modifier
|
||||||
|
List<ClaimModifier> claimModifiers = UserInfoUtils.loadClaimValueModifiers(
|
||||||
|
properties, claimName, propertyBase);
|
||||||
|
//add claim definition
|
||||||
|
customClaims.add(new PerunCustomClaimDefinition(scope, claimName, claimSource, claimModifiers));
|
||||||
|
}
|
||||||
|
return customClaims;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClaimSource loadClaimSource(Properties properties,
|
||||||
|
PerunOidcConfig perunOidcConfig,
|
||||||
|
JWTSigningAndValidationService jwtService,
|
||||||
|
String claimName,
|
||||||
|
String propertyBase)
|
||||||
|
throws ConfigurationException
|
||||||
|
{
|
||||||
|
String propertyPrefix = propertyBase + SOURCE;
|
||||||
|
String sourceClass = properties.getProperty(propertyPrefix + CLASS);
|
||||||
|
if (!StringUtils.hasText(sourceClass)) {
|
||||||
|
log.error("{} - failed to initialized claim source: no class has ben configured", claimName);
|
||||||
|
throw new ConfigurationException("No class configured for claim source");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.trace("{} - loading ClaimSource class '{}'", claimName, sourceClass);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> rawClazz = Class.forName(sourceClass);
|
||||||
|
if (!ClaimSource.class.isAssignableFrom(rawClazz)) {
|
||||||
|
log.error("{} - failed to initialized claim source: class '{}' does not extend ClaimSource",
|
||||||
|
claimName, sourceClass);
|
||||||
|
throw new ConfigurationException("No instantiable class source configured for claim " + claimName);
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked") Class<ClaimSource> clazz = (Class<ClaimSource>) rawClazz;
|
||||||
|
Constructor<ClaimSource> constructor = clazz.getConstructor(ClaimSourceInitContext.class);
|
||||||
|
ClaimSourceInitContext ctx = new ClaimSourceInitContext(perunOidcConfig, jwtService, propertyPrefix,
|
||||||
|
properties, claimName);
|
||||||
|
return constructor.newInstance(ctx);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
log.error("{} - failed to initialize claim source: class '{}' was not found", claimName, sourceClass);
|
||||||
|
log.trace("{} - details:", claimName, e);
|
||||||
|
throw new ConfigurationException("Error has occurred when instantiating claim source for claim " + claimName);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
log.error("{} - failed to initialize claim source: class '{}' does not have proper constructor",
|
||||||
|
claimName, sourceClass);
|
||||||
|
log.trace("{} - details:", claimName, e);
|
||||||
|
throw new ConfigurationException("Error has occurred when instantiating claim source for claim " + claimName);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
|
||||||
|
log.error("{} - failed to initialize claim source: class '{}' cannot be instantiated", claimName, sourceClass);
|
||||||
|
log.trace("{} - details:", claimName, e);
|
||||||
|
throw new ConfigurationException("Error has occurred when instantiating claim source for claim " + claimName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ClaimModifier> loadClaimValueModifiers(Properties properties,
|
||||||
|
String claimName,
|
||||||
|
String propertyBase)
|
||||||
|
throws ConfigurationException
|
||||||
|
{
|
||||||
|
String propertyPrefix = propertyBase + MODIFIER;
|
||||||
|
String names = properties.getProperty(propertyPrefix + NAMES, "");
|
||||||
|
String[] nameArr = names.split(",");
|
||||||
|
List<ClaimModifier> modifiers = new ArrayList<>();
|
||||||
|
if (nameArr.length > 0) {
|
||||||
|
for (String name : nameArr) {
|
||||||
|
modifiers.add(loadClaimValueModifier(properties, claimName, propertyPrefix + '.' + name, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClaimModifier loadClaimValueModifier(Properties properties,
|
||||||
|
String claimName,
|
||||||
|
String propertyPrefix,
|
||||||
|
String modifierName)
|
||||||
|
throws ConfigurationException
|
||||||
|
{
|
||||||
|
String modifierClass = properties.getProperty(propertyPrefix + CLASS, NoOperationModifier.class.getName());
|
||||||
|
if (!StringUtils.hasText(modifierClass)) {
|
||||||
|
log.debug("{}:{} - no class has ben configured for claim value modifier, use noop modifier",
|
||||||
|
claimName, modifierName);
|
||||||
|
modifierClass = NoOperationModifier.class.getName();
|
||||||
|
}
|
||||||
|
log.trace("{}:{} - loading ClaimModifier class '{}'", claimName, modifierName, modifierClass);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> rawClazz = Class.forName(modifierClass);
|
||||||
|
if (!ClaimModifier.class.isAssignableFrom(rawClazz)) {
|
||||||
|
log.error("{}:{} - failed to initialized claim modifier: class '{}' does not extend ClaimModifier",
|
||||||
|
claimName, modifierName, modifierClass);
|
||||||
|
throw new ConfigurationException("No instantiable class modifier configured for claim " + claimName);
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked") Class<ClaimModifier> clazz = (Class<ClaimModifier>) rawClazz;
|
||||||
|
Constructor<ClaimModifier> constructor = clazz.getConstructor(ClaimModifierInitContext.class);
|
||||||
|
ClaimModifierInitContext ctx = new ClaimModifierInitContext(
|
||||||
|
propertyPrefix, properties, claimName, modifierName);
|
||||||
|
return constructor.newInstance(ctx);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
log.error("{}:{} - failed to initialize claim modifier: class '{}' was not found",
|
||||||
|
claimName, modifierName, modifierClass);
|
||||||
|
log.trace("{}:{} - details:", claimName, modifierName, e);
|
||||||
|
throw new ConfigurationException("Error has occurred when instantiating claim modifier '"
|
||||||
|
+ modifierName + "' of claim '" + claimName + '\'');
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
log.error("{}:{} - failed to initialize claim modifier: class '{}' does not have proper constructor",
|
||||||
|
claimName, modifierName, modifierClass);
|
||||||
|
log.trace("{}:{} - details:", claimName, e);
|
||||||
|
throw new ConfigurationException("Error has occurred when instantiating claim modifier '"
|
||||||
|
+ modifierName + "' of claim '" + claimName + '\'');
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
|
||||||
|
log.error("{}:{} - failed to initialize claim modifier: class '{}' cannot be instantiated",
|
||||||
|
claimName, modifierName, modifierClass);
|
||||||
|
log.trace("{}:{} - details:", claimName, e);
|
||||||
|
throw new ConfigurationException("Error has occurred when instantiating claim modifier '"
|
||||||
|
+ modifierName + "' of claim '" + claimName + '\'');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package cz.muni.ics.oidc.server.userInfo.mappings;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class AddressMappings {
|
||||||
|
|
||||||
|
private String formatted = null;
|
||||||
|
private String streetAddress = null;
|
||||||
|
private String locality = null;
|
||||||
|
private String region = null;
|
||||||
|
private String postalCode = null;
|
||||||
|
private String country = null;
|
||||||
|
|
||||||
|
public Set<String> getAttrNames() {
|
||||||
|
return new HashSet<>(Arrays.asList(formatted, streetAddress, locality, region, postalCode, country));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package cz.muni.ics.oidc.server.userInfo.mappings;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class EmailMappings {
|
||||||
|
|
||||||
|
private String email = null;
|
||||||
|
private String emailVerified = null;
|
||||||
|
|
||||||
|
public Set<String> getAttrNames() {
|
||||||
|
return new HashSet<>(Arrays.asList(email, emailVerified));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package cz.muni.ics.oidc.server.userInfo.mappings;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class OpenidMappings {
|
||||||
|
|
||||||
|
private String sub = null;
|
||||||
|
|
||||||
|
public Set<String> getAttrNames() {
|
||||||
|
return new HashSet<>(Arrays.asList(sub));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package cz.muni.ics.oidc.server.userInfo.mappings;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class PhoneMappings {
|
||||||
|
|
||||||
|
private String phoneNumber = null;
|
||||||
|
private String phoneNumberVerified = null;
|
||||||
|
|
||||||
|
public Set<String> getAttrNames() {
|
||||||
|
return new HashSet<>(Arrays.asList(phoneNumber, phoneNumberVerified));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package cz.muni.ics.oidc.server.userInfo.mappings;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class ProfileMappings {
|
||||||
|
|
||||||
|
private String name = null;
|
||||||
|
private String familyName = null;
|
||||||
|
private String givenName = null;
|
||||||
|
private String middleName = null;
|
||||||
|
private String nickname = null;
|
||||||
|
private String preferredUsername = null;
|
||||||
|
private String profile = null;
|
||||||
|
private String picture = null;
|
||||||
|
private String website = null;
|
||||||
|
private String gender = null;
|
||||||
|
private String birthdate = null;
|
||||||
|
private String zoneinfo = null;
|
||||||
|
private String locale = null;
|
||||||
|
|
||||||
|
public Set<String> getAttrNames() {
|
||||||
|
return new HashSet<>(Arrays.asList(name, familyName, givenName, middleName, nickname, preferredUsername,
|
||||||
|
profile, picture, website, gender, birthdate, zoneinfo, locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
package cz.muni.ics.oidc.server.userInfo;
|
package cz.muni.ics.oidc.server.userInfo.modifiers;
|
||||||
|
|
||||||
|
import cz.muni.ics.oidc.server.userInfo.PerunUserInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for all code that needs to modify user info.
|
* Interface for all code that needs to modify user info.
|
|
@ -1,6 +1,7 @@
|
||||||
package cz.muni.ics.oidc.server.userInfo;
|
package cz.muni.ics.oidc.server.userInfo.modifiers;
|
||||||
|
|
||||||
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
|
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
|
||||||
|
import cz.muni.ics.oidc.server.userInfo.PerunUserInfo;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
|
@ -1,4 +1,4 @@
|
||||||
package cz.muni.ics.oidc.server.userInfo;
|
package cz.muni.ics.oidc.server.userInfo.modifiers;
|
||||||
|
|
||||||
|
|
||||||
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
|
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
|
|
@ -1,5 +1,8 @@
|
||||||
package cz.muni.ics.oidc.web.controllers;
|
package cz.muni.ics.oidc.web.controllers;
|
||||||
|
|
||||||
|
import static cz.muni.ics.oauth2.web.OAuthConfirmationController.CLAIMS;
|
||||||
|
import static cz.muni.ics.oauth2.web.OAuthConfirmationController.SCOPES;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
|
@ -10,6 +13,7 @@ import cz.muni.ics.oauth2.service.SystemScopeService;
|
||||||
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
|
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
|
||||||
import cz.muni.ics.oidc.web.WebHtmlClasses;
|
import cz.muni.ics.oidc.web.WebHtmlClasses;
|
||||||
import cz.muni.ics.openid.connect.model.UserInfo;
|
import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
|
import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
@ -153,20 +157,9 @@ public class ControllerUtils {
|
||||||
ScopeClaimTranslationService translationService,
|
ScopeClaimTranslationService translationService,
|
||||||
Map<String, Object> model,
|
Map<String, Object> model,
|
||||||
Set<String> scope,
|
Set<String> scope,
|
||||||
UserInfo user) {
|
UserInfo user)
|
||||||
Set<SystemScope> scopes = scopeService.fromStrings(scope);
|
{
|
||||||
Set<SystemScope> sortedScopes = new LinkedHashSet<>(scopes.size());
|
Set<SystemScope> sortedScopes = ControllerUtils.getSortedScopes(scope, scopeService);
|
||||||
Set<SystemScope> systemScopes = scopeService.getAll();
|
|
||||||
|
|
||||||
// sort scopes for display based on the inherent order of system scopes
|
|
||||||
for (SystemScope s : systemScopes) {
|
|
||||||
if (scopes.contains(s)) {
|
|
||||||
sortedScopes.add(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add in any scopes that aren't system scopes to the end of the list
|
|
||||||
sortedScopes.addAll(Sets.difference(scopes, systemScopes));
|
|
||||||
|
|
||||||
Map<String, Map<String, Object>> claimsForScopes = new LinkedHashMap<>();
|
Map<String, Map<String, Object>> claimsForScopes = new LinkedHashMap<>();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
@ -208,8 +201,8 @@ public class ControllerUtils {
|
||||||
})
|
})
|
||||||
.sorted((o1, o2) -> compareByClaimsAmount(o1, o2, claimsForScopes))
|
.sorted((o1, o2) -> compareByClaimsAmount(o1, o2, claimsForScopes))
|
||||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
model.put("claims", claimsForScopes);
|
model.put(CLAIMS, claimsForScopes);
|
||||||
model.put("scopes", sortedScopes);
|
model.put(SCOPES, sortedScopes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,7 +17,10 @@
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package cz.muni.ics.openid.connect.service;
|
package cz.muni.ics.openid.connect.service;
|
||||||
|
|
||||||
|
import cz.muni.ics.oauth2.model.SavedUserAuthentication;
|
||||||
import cz.muni.ics.openid.connect.model.UserInfo;
|
import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.springframework.security.saml.SAMLCredential;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for UserInfo service
|
* Interface for UserInfo service
|
||||||
|
@ -27,30 +30,10 @@ import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
*/
|
*/
|
||||||
public interface UserInfoService {
|
public interface UserInfoService {
|
||||||
|
|
||||||
/**
|
UserInfo get(String username, String clientId, Set<String> scope, SavedUserAuthentication userAuthentication);
|
||||||
* Get the UserInfo for the given username (usually maps to the
|
|
||||||
* preferredUsername field).
|
|
||||||
* @param username
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
UserInfo getByUsername(String username);
|
|
||||||
|
|
||||||
/**
|
UserInfo get(String username, String clientId, Set<String> scope, SAMLCredential samlCredential);
|
||||||
* Get the UserInfo for the given username (usually maps to the
|
|
||||||
* preferredUsername field) and clientId. This allows pairwise
|
|
||||||
* client identifiers where appropriate.
|
|
||||||
* @param username
|
|
||||||
* @param clientId
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
UserInfo getByUsernameAndClientId(String username, String clientId);
|
|
||||||
|
|
||||||
/**
|
UserInfo get(String username, String clientId, Set<String> scope);
|
||||||
* Get the user registered at this server with the given email address.
|
|
||||||
*
|
|
||||||
* @param email
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
UserInfo getByEmailAddress(String email);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,13 +18,17 @@
|
||||||
package cz.muni.ics.openid.connect.service.impl;
|
package cz.muni.ics.openid.connect.service.impl;
|
||||||
|
|
||||||
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
||||||
|
import cz.muni.ics.oauth2.model.SavedUserAuthentication;
|
||||||
|
import cz.muni.ics.oauth2.model.SystemScope;
|
||||||
import cz.muni.ics.oauth2.model.enums.SubjectType;
|
import cz.muni.ics.oauth2.model.enums.SubjectType;
|
||||||
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
|
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
|
||||||
import cz.muni.ics.openid.connect.model.UserInfo;
|
import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
import cz.muni.ics.openid.connect.repository.UserInfoRepository;
|
import cz.muni.ics.openid.connect.repository.UserInfoRepository;
|
||||||
import cz.muni.ics.openid.connect.service.PairwiseIdentiferService;
|
import cz.muni.ics.openid.connect.service.PairwiseIdentiferService;
|
||||||
import cz.muni.ics.openid.connect.service.UserInfoService;
|
import cz.muni.ics.openid.connect.service.UserInfoService;
|
||||||
|
import java.util.Set;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.saml.SAMLCredential;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,7 +37,6 @@ import org.springframework.stereotype.Service;
|
||||||
* @author Michael Joseph Walsh, jricher
|
* @author Michael Joseph Walsh, jricher
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Service
|
|
||||||
public class DefaultUserInfoService implements UserInfoService {
|
public class DefaultUserInfoService implements UserInfoService {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -46,20 +49,31 @@ public class DefaultUserInfoService implements UserInfoService {
|
||||||
private PairwiseIdentiferService pairwiseIdentifierService;
|
private PairwiseIdentiferService pairwiseIdentifierService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserInfo getByUsername(String username) {
|
public UserInfo get(String username, String clientId, Set<String> scope, SavedUserAuthentication userAuthentication) {
|
||||||
return userInfoRepository.getByUsername(username);
|
return getByUsernameAndClientId(username, clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserInfo getByUsernameAndClientId(String username, String clientId) {
|
public UserInfo get(String username, String clientId, Set<String> scope, SAMLCredential samlCredential) {
|
||||||
|
return getByUsernameAndClientId(username, clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserInfo get(String username, String clientId, Set<String> scope) {
|
||||||
|
return getByUsernameAndClientId(username, clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserInfo getByUsernameAndClientId(String username, String clientId) {
|
||||||
|
|
||||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||||
|
if (client == null) {
|
||||||
UserInfo userInfo = getByUsername(username);
|
|
||||||
|
|
||||||
if (client == null || userInfo == null) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
UserInfo userInfo = userInfoRepository.getByUsername(username);
|
||||||
|
|
||||||
|
if (userInfo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (SubjectType.PAIRWISE.equals(client.getSubjectType())) {
|
if (SubjectType.PAIRWISE.equals(client.getSubjectType())) {
|
||||||
String pairwiseSub = pairwiseIdentifierService.getIdentifier(userInfo, client);
|
String pairwiseSub = pairwiseIdentifierService.getIdentifier(userInfo, client);
|
||||||
|
@ -70,9 +84,4 @@ public class DefaultUserInfoService implements UserInfoService {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserInfo getByEmailAddress(String email) {
|
|
||||||
return userInfoRepository.getByEmailAddress(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,7 @@
|
||||||
package cz.muni.ics.openid.connect.service.impl;
|
package cz.muni.ics.openid.connect.service.impl;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import cz.muni.ics.openid.connect.model.UserInfo;
|
|
||||||
import cz.muni.ics.openid.connect.service.LoginHintExtracter;
|
import cz.muni.ics.openid.connect.service.LoginHintExtracter;
|
||||||
import cz.muni.ics.openid.connect.service.UserInfoService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the login hint against the User Info collection, only populates it if a user is found.
|
* Checks the login hint against the User Info collection, only populates it if a user is found.
|
||||||
|
@ -29,9 +26,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||||
*/
|
*/
|
||||||
public class MatchLoginHintsAgainstUsers implements LoginHintExtracter {
|
public class MatchLoginHintsAgainstUsers implements LoginHintExtracter {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserInfoService userInfoService;
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see cz.muni.ics.openid.connect.service.LoginHintTester#useHint(java.lang.String)
|
* @see cz.muni.ics.openid.connect.service.LoginHintTester#useHint(java.lang.String)
|
||||||
*/
|
*/
|
||||||
|
@ -39,19 +33,8 @@ public class MatchLoginHintsAgainstUsers implements LoginHintExtracter {
|
||||||
public String extractHint(String loginHint) {
|
public String extractHint(String loginHint) {
|
||||||
if (Strings.isNullOrEmpty(loginHint)) {
|
if (Strings.isNullOrEmpty(loginHint)) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
|
||||||
UserInfo user = userInfoService.getByEmailAddress(loginHint);
|
|
||||||
if (user == null) {
|
|
||||||
user = userInfoService.getByUsername(loginHint);
|
|
||||||
if (user == null) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return user.getPreferredUsername();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return user.getPreferredUsername();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return loginHint;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
|
||||||
&& !authentication.isClientOnly()) {
|
&& !authentication.isClientOnly()) {
|
||||||
|
|
||||||
String username = authentication.getName();
|
String username = authentication.getName();
|
||||||
UserInfo userInfo = userInfoService.getByUsernameAndClientId(username, clientId);
|
UserInfo userInfo = userInfoService.get(username, clientId, originalAuthRequest.getScope(), token.getAuthenticationHolder().getUserAuth());
|
||||||
|
|
||||||
if (userInfo != null) {
|
if (userInfo != null) {
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ package cz.muni.ics.openid.connect.web;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
||||||
|
import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
|
||||||
|
import cz.muni.ics.oauth2.model.SavedUserAuthentication;
|
||||||
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
|
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
|
||||||
import cz.muni.ics.oauth2.service.SystemScopeService;
|
import cz.muni.ics.oauth2.service.SystemScopeService;
|
||||||
import cz.muni.ics.openid.connect.model.UserInfo;
|
import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
|
@ -76,7 +78,9 @@ public class UserInfoEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = auth.getName();
|
String username = auth.getName();
|
||||||
UserInfo userInfo = userInfoService.getByUsernameAndClientId(username, auth.getOAuth2Request().getClientId());
|
UserInfo userInfo = userInfoService.get(username, auth.getOAuth2Request().getClientId(),
|
||||||
|
auth.getOAuth2Request().getScope(),
|
||||||
|
(SavedUserAuthentication)auth.getUserAuthentication());
|
||||||
|
|
||||||
if (userInfo == null) {
|
if (userInfo == null) {
|
||||||
log.error("getInfo failed; user not found: " + username);
|
log.error("getInfo failed; user not found: " + username);
|
||||||
|
|
|
@ -20,21 +20,35 @@
|
||||||
*/
|
*/
|
||||||
package cz.muni.ics.openid.connect.web;
|
package cz.muni.ics.openid.connect.web;
|
||||||
|
|
||||||
|
import static cz.muni.ics.openid.connect.request.ConnectRequestParameters.CLIENT_ID;
|
||||||
|
import static cz.muni.ics.openid.connect.request.ConnectRequestParameters.SCOPE;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonPrimitive;
|
import com.google.gson.JsonPrimitive;
|
||||||
import com.google.gson.JsonSerializer;
|
import com.google.gson.JsonSerializer;
|
||||||
|
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
||||||
|
import cz.muni.ics.oauth2.model.SamlAuthenticationDetails;
|
||||||
|
import cz.muni.ics.oauth2.model.SystemScope;
|
||||||
|
import cz.muni.ics.oidc.models.PerunUser;
|
||||||
|
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
|
||||||
import cz.muni.ics.openid.connect.model.OIDCAuthenticationToken;
|
import cz.muni.ics.openid.connect.model.OIDCAuthenticationToken;
|
||||||
import cz.muni.ics.openid.connect.model.UserInfo;
|
import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
|
import cz.muni.ics.openid.connect.request.ConnectRequestParameters;
|
||||||
import cz.muni.ics.openid.connect.service.UserInfoService;
|
import cz.muni.ics.openid.connect.service.UserInfoService;
|
||||||
|
import java.util.Set;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||||
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
|
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
|
||||||
|
import org.springframework.security.oauth2.common.util.OAuth2Utils;
|
||||||
|
import org.springframework.security.saml.SAMLCredential;
|
||||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,6 +57,7 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||||
* @author jricher
|
* @author jricher
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class UserInfoInterceptor extends HandlerInterceptorAdapter {
|
public class UserInfoInterceptor extends HandlerInterceptorAdapter {
|
||||||
|
|
||||||
private final Gson gson = new GsonBuilder()
|
private final Gson gson = new GsonBuilder()
|
||||||
|
@ -50,13 +65,13 @@ public class UserInfoInterceptor extends HandlerInterceptorAdapter {
|
||||||
(JsonSerializer<GrantedAuthority>) (src, typeOfSrc, context) -> new JsonPrimitive(src.getAuthority()))
|
(JsonSerializer<GrantedAuthority>) (src, typeOfSrc, context) -> new JsonPrimitive(src.getAuthority()))
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired
|
||||||
private UserInfoService userInfoService;
|
private UserInfoService userInfoService;
|
||||||
|
|
||||||
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
if (auth != null){
|
if (auth != null){
|
||||||
|
@ -75,12 +90,25 @@ public class UserInfoInterceptor extends HandlerInterceptorAdapter {
|
||||||
request.setAttribute("userInfoJson", "null");
|
request.setAttribute("userInfoJson", "null");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// don't bother checking if we don't have a principal or a userInfoService to work with
|
if (auth == null || auth.getName() == null || userInfoService == null) {
|
||||||
if (auth != null && auth.getName() != null && userInfoService != null) {
|
log.debug("No point to handle, skip");
|
||||||
UserInfo user = userInfoService.getByUsername(auth.getName());
|
} else {
|
||||||
if (user != null) {
|
if (request.getAttribute("userInfo") == null && request.getAttribute("userInfoJson") == null) {
|
||||||
request.setAttribute("userInfo", user);
|
String clientId = request.getParameter(CLIENT_ID);
|
||||||
request.setAttribute("userInfoJson", user.toJson());
|
if (clientId == null) {
|
||||||
|
log.debug("No client provided, no reason to continue processing");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Set<String> scopes = OAuth2Utils.parseParameterList(request.getParameter(ConnectRequestParameters.SCOPE));
|
||||||
|
UserInfo user = userInfoService.get(auth.getName(), clientId, scopes, (SAMLCredential) auth.getCredentials());
|
||||||
|
if (user != null) {
|
||||||
|
request.setAttribute("userInfo", user);
|
||||||
|
request.setAttribute("userInfoJson", user.toJson());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug("Already has userInfo or userInfoJson");
|
||||||
|
log.trace("userInfo: {}", request.getAttribute("userInfo"));
|
||||||
|
log.trace("userInfoJson: {}", request.getAttribute("userInfoJson"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import cz.muni.ics.openid.connect.model.DefaultUserInfo;
|
||||||
import cz.muni.ics.openid.connect.model.UserInfo;
|
import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
import cz.muni.ics.openid.connect.repository.UserInfoRepository;
|
import cz.muni.ics.openid.connect.repository.UserInfoRepository;
|
||||||
import cz.muni.ics.openid.connect.service.PairwiseIdentiferService;
|
import cz.muni.ics.openid.connect.service.PairwiseIdentiferService;
|
||||||
|
import java.util.HashSet;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -140,42 +141,6 @@ public class TestDefaultUserInfoService {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test loading an admin user, ensuring that the UserDetails object returned
|
|
||||||
* has both the ROLE_USER and ROLE_ADMIN authorities.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void loadByUsername_admin_success() {
|
|
||||||
Mockito.when(userInfoRepository.getByUsername(adminUsername)).thenReturn(userInfoAdmin);
|
|
||||||
UserInfo user = service.getByUsername(adminUsername);
|
|
||||||
assertEquals(user.getSub(), adminSub);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test loading a regular, non-admin user, ensuring that the returned UserDetails
|
|
||||||
* object has ROLE_USER but *not* ROLE_ADMIN.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void loadByUsername_regular_success() {
|
|
||||||
|
|
||||||
Mockito.when(userInfoRepository.getByUsername(regularUsername)).thenReturn(userInfoRegular);
|
|
||||||
UserInfo user = service.getByUsername(regularUsername);
|
|
||||||
assertEquals(user.getSub(), regularSub);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If a user is not found, the loadByUsername method should throw an exception.
|
|
||||||
*/
|
|
||||||
@Test()
|
|
||||||
public void loadByUsername_nullUser() {
|
|
||||||
|
|
||||||
Mockito.when(userInfoRepository.getByUsername(adminUsername)).thenReturn(null);
|
|
||||||
UserInfo user = service.getByUsername(adminUsername);
|
|
||||||
|
|
||||||
assertNull(user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -191,8 +156,8 @@ public class TestDefaultUserInfoService {
|
||||||
|
|
||||||
Mockito.verify(pairwiseIdentiferService, Mockito.never()).getIdentifier(any(UserInfo.class), any(ClientDetailsEntity.class));
|
Mockito.verify(pairwiseIdentiferService, Mockito.never()).getIdentifier(any(UserInfo.class), any(ClientDetailsEntity.class));
|
||||||
|
|
||||||
UserInfo user1 = service.getByUsernameAndClientId(regularUsername, publicClientId1);
|
UserInfo user1 = service.get(regularUsername, publicClientId1, new HashSet<>());
|
||||||
UserInfo user2 = service.getByUsernameAndClientId(regularUsername, publicClientId2);
|
UserInfo user2 = service.get(regularUsername, publicClientId2, new HashSet<>());
|
||||||
|
|
||||||
assertEquals(regularSub, user1.getSub());
|
assertEquals(regularSub, user1.getSub());
|
||||||
assertEquals(regularSub, user2.getSub());
|
assertEquals(regularSub, user2.getSub());
|
||||||
|
@ -225,10 +190,10 @@ public class TestDefaultUserInfoService {
|
||||||
Mockito.when(pairwiseIdentiferService.getIdentifier(userInfoRegular, pairwiseClient3)).thenReturn(pairwiseSub3);
|
Mockito.when(pairwiseIdentiferService.getIdentifier(userInfoRegular, pairwiseClient3)).thenReturn(pairwiseSub3);
|
||||||
Mockito.when(pairwiseIdentiferService.getIdentifier(userInfoRegular, pairwiseClient4)).thenReturn(pairwiseSub4);
|
Mockito.when(pairwiseIdentiferService.getIdentifier(userInfoRegular, pairwiseClient4)).thenReturn(pairwiseSub4);
|
||||||
|
|
||||||
UserInfo user1 = service.getByUsernameAndClientId(regularUsername, pairwiseClientId1);
|
UserInfo user1 = service.get(regularUsername, pairwiseClientId1, new HashSet<>());
|
||||||
UserInfo user2 = service.getByUsernameAndClientId(regularUsername, pairwiseClientId2);
|
UserInfo user2 = service.get(regularUsername, pairwiseClientId2, new HashSet<>());
|
||||||
UserInfo user3 = service.getByUsernameAndClientId(regularUsername, pairwiseClientId3);
|
UserInfo user3 = service.get(regularUsername, pairwiseClientId3, new HashSet<>());
|
||||||
UserInfo user4 = service.getByUsernameAndClientId(regularUsername, pairwiseClientId4);
|
UserInfo user4 = service.get(regularUsername, pairwiseClientId4, new HashSet<>());
|
||||||
|
|
||||||
assertEquals(pairwiseSub12, user1.getSub());
|
assertEquals(pairwiseSub12, user1.getSub());
|
||||||
assertEquals(pairwiseSub12, user2.getSub());
|
assertEquals(pairwiseSub12, user2.getSub());
|
||||||
|
|
Loading…
Reference in New Issue