Commit 9ee4910c authored by michael.simon's avatar michael.simon
Browse files

implement basic oidc token refresh routine

Following cases are implemented for now:
* Refresh token changes, only allowed for once
* Refresh token does not change, allowed multiple times
* Claims missing, UserInfo existant
* Claims existant and signed (Signature is checked), UserInfo existant
parent 6f518292
......@@ -7,7 +7,6 @@ import java.util.Map;
import javax.enterprise.context.ApplicationScoped;
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.oauth2.sdk.token.RefreshToken;
import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
......@@ -19,22 +18,32 @@ public class OidcTokenHelper {
public Map<String, List<Object>> convertToAttributeMap(IDTokenClaimsSet claims, UserInfo userInfo, RefreshToken refreshToken, BearerAccessToken bat) {
Map<String, List<Object>> attributeMap = new HashMap<String, List<Object>>();
List<Object> tempList = new ArrayList<Object>();
tempList.add(claims);
attributeMap.put("claims", tempList);
tempList = new ArrayList<Object>();
tempList.add(userInfo);
attributeMap.put("userInfo", tempList);
tempList = new ArrayList<Object>();
tempList.add(refreshToken);
attributeMap.put("refreshToken", tempList);
tempList = new ArrayList<Object>();
tempList.add(bat);
attributeMap.put("bearerAccessToken", tempList);
List<Object> tempList;
if (claims != null) {
tempList = new ArrayList<Object>();
tempList.add(claims);
attributeMap.put("claims", tempList);
}
if (userInfo != null) {
tempList = new ArrayList<Object>();
tempList.add(userInfo);
attributeMap.put("userInfo", tempList);
}
if (refreshToken != null) {
tempList = new ArrayList<Object>();
tempList.add(refreshToken.getValue());
attributeMap.put("refreshToken", tempList);
}
if (bat != null) {
tempList = new ArrayList<Object>();
tempList.add(bat);
attributeMap.put("bearerAccessToken", tempList);
}
return attributeMap;
}
......
/*******************************************************************************
* Copyright (c) 2014 Michael Simon.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Michael Simon - initial
******************************************************************************/
package edu.kit.scc.webreg.service.oidc.client;
import edu.kit.scc.webreg.entity.oidc.OidcUserEntity;
import edu.kit.scc.webreg.exc.UserUpdateException;
import edu.kit.scc.webreg.service.BaseService;
public interface OidcUserService extends BaseService<OidcUserEntity, Long> {
OidcUserEntity updateUserFromOp(OidcUserEntity user, String executor) throws UserUpdateException;
}
/*******************************************************************************
* Copyright (c) 2014 Michael Simon.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Michael Simon - initial
******************************************************************************/
package edu.kit.scc.webreg.service.oidc.client;
import java.io.Serializable;
import javax.ejb.Stateless;
import javax.inject.Inject;
import edu.kit.scc.webreg.dao.BaseDao;
import edu.kit.scc.webreg.dao.oidc.OidcUserDao;
import edu.kit.scc.webreg.entity.oidc.OidcUserEntity;
import edu.kit.scc.webreg.exc.UserUpdateException;
import edu.kit.scc.webreg.service.impl.BaseServiceImpl;
@Stateless
public class OidcUserServiceImpl extends BaseServiceImpl<OidcUserEntity, Long> implements OidcUserService, Serializable {
private static final long serialVersionUID = 1L;
@Inject
private OidcUserDao dao;
@Inject
private OidcUserUpdater userUpdater;
@Override
public OidcUserEntity updateUserFromOp(OidcUserEntity user, String executor) throws UserUpdateException {
return userUpdater.updateUserFromOP(user, executor);
}
@Override
protected BaseDao<OidcUserEntity, Long> getDao() {
return dao;
}
}
package edu.kit.scc.webreg.service.oidc.client;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
......@@ -19,10 +20,32 @@ import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.MDC;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.proc.BadJOSEException;
import com.nimbusds.jwt.JWT;
import com.nimbusds.oauth2.sdk.AuthorizationGrant;
import com.nimbusds.oauth2.sdk.ErrorObject;
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.RefreshTokenGrant;
import com.nimbusds.oauth2.sdk.TokenErrorResponse;
import com.nimbusds.oauth2.sdk.TokenRequest;
import com.nimbusds.oauth2.sdk.TokenResponse;
import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
import com.nimbusds.oauth2.sdk.auth.Secret;
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.oauth2.sdk.token.RefreshToken;
import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
import com.nimbusds.openid.connect.sdk.UserInfoRequest;
import com.nimbusds.openid.connect.sdk.UserInfoResponse;
import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator;
import edu.kit.scc.webreg.audit.Auditor;
import edu.kit.scc.webreg.audit.RegistryAuditor;
......@@ -42,6 +65,7 @@ import edu.kit.scc.webreg.entity.UserStatus;
import edu.kit.scc.webreg.entity.as.ASUserAttrEntity;
import edu.kit.scc.webreg.entity.as.AttributeSourceServiceEntity;
import edu.kit.scc.webreg.entity.audit.AuditStatus;
import edu.kit.scc.webreg.entity.oidc.OidcRpConfigurationEntity;
import edu.kit.scc.webreg.entity.oidc.OidcUserEntity;
import edu.kit.scc.webreg.event.EventSubmitter;
import edu.kit.scc.webreg.event.UserEvent;
......@@ -106,15 +130,86 @@ public class OidcUserUpdater implements Serializable {
@Inject
private OidcTokenHelper oidcTokenHelper;
public OidcUserEntity updateUserFromOP(OidcUserEntity user, String executor) {
@Inject
private OidcOpMetadataSingletonBean opMetadataBean;
public OidcUserEntity updateUserFromOP(OidcUserEntity user, String executor)
throws UserUpdateException {
user = userDao.merge(user);
/**
* TODO Implement refresh here
*/
user.setScheduledUpdate(getNextScheduledUpdate());
try {
/**
* TODO Implement refresh here
*/
OidcRpConfigurationEntity rpConfig = user.getIssuer();
RefreshToken token = new RefreshToken(user.getAttributeStore().get("refreshToken"));
AuthorizationGrant refreshTokenGrant = new RefreshTokenGrant(token);
ClientID clientID = new ClientID(user.getIssuer().getClientId());
Secret clientSecret = new Secret(user.getIssuer().getSecret());
ClientAuthentication clientAuth = new ClientSecretBasic(clientID, clientSecret);
TokenRequest tokenRequest = new TokenRequest(opMetadataBean.getTokenEndpointURI(user.getIssuer()),
clientAuth, refreshTokenGrant);
TokenResponse tokenResponse = OIDCTokenResponseParser.parse(tokenRequest.toHTTPRequest().send());
if (! tokenResponse.indicatesSuccess()) {
TokenErrorResponse errorResponse = tokenResponse.toErrorResponse();
ErrorObject error = errorResponse.getErrorObject();
logger.info("Got error: code {}, desc {}, http-status {}, uri {}", error.getCode(), error.getDescription());
}
else {
OIDCTokenResponse oidcTokenResponse = (OIDCTokenResponse) tokenResponse.toSuccessResponse();
logger.debug("response: {}", oidcTokenResponse.toString());
JWT idToken = oidcTokenResponse.getOIDCTokens().getIDToken();
IDTokenClaimsSet claims = null;
if (idToken != null) {
IDTokenValidator validator = new IDTokenValidator(
new Issuer(rpConfig.getServiceUrl()),
new ClientID(rpConfig.getClientId()),
JWSAlgorithm.RS256,
opMetadataBean.getJWKSetURI(rpConfig).toURL());
try {
claims = validator.validate(idToken, null);
logger.debug("Got signed claims verified from {}: {}", claims.getIssuer(), claims.getSubject());
} catch (BadJOSEException | JOSEException e) {
throw new UserUpdateException("signature failed: " + e.getMessage());
}
}
RefreshToken refreshToken = oidcTokenResponse.getOIDCTokens().getRefreshToken();
BearerAccessToken bearerAccessToken = oidcTokenResponse.getOIDCTokens().getBearerAccessToken();
HTTPResponse httpResponse = new UserInfoRequest(
opMetadataBean.getUserInfoEndpointURI(rpConfig), bearerAccessToken)
.toHTTPRequest()
.send();
UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse);
if (! userInfoResponse.indicatesSuccess()) {
throw new UserUpdateException("got userinfo error response: " +
userInfoResponse.toErrorResponse().getErrorObject().getDescription());
}
UserInfo userInfo = userInfoResponse.toSuccessResponse().getUserInfo();
logger.info("userinfo {}, {}, {}", userInfo.getSubject(), userInfo.getPreferredUsername(),
userInfo.getEmailAddress());
logger.debug("Updating OIDC user {}", user.getSubjectId());
user = updateUser(user, claims, userInfo, refreshToken, bearerAccessToken, "web-sso");
}
}
catch (IOException | ParseException e) {
logger.warn("Exception!", e);
}
return user;
}
......@@ -230,7 +325,6 @@ public class OidcUserUpdater implements Serializable {
}
Map<String, String> attributeStore = user.getAttributeStore();
attributeStore.clear();
for (Entry<String, List<Object>> entry : attributeMap.entrySet()) {
attributeStore.put(entry.getKey(), attrHelper.attributeListToString(entry.getValue()));
}
......@@ -318,7 +412,7 @@ public class OidcUserUpdater implements Serializable {
if (completeOverrideHook == null) {
IDTokenClaimsSet claims = oidcTokenHelper.claimsFromMap(attributeMap);
if (claims == null) {
throw new UserUpdateException("ID claims are missing in session");
logger.info("No claims set for user {}", user.getId());
}
UserInfo userInfo = oidcTokenHelper.userInfoFromMap(attributeMap);
......
......@@ -24,7 +24,6 @@ import javax.inject.Inject;
import org.opensaml.saml.saml2.core.Attribute;
import org.primefaces.event.TransferEvent;
import org.primefaces.model.DualListModel;
import org.primefaces.model.FilterMeta;
import org.primefaces.model.LazyDataModel;
import org.slf4j.Logger;
......@@ -40,6 +39,7 @@ import edu.kit.scc.webreg.entity.UserEntity;
import edu.kit.scc.webreg.entity.as.ASUserAttrEntity;
import edu.kit.scc.webreg.entity.as.AttributeSourceEntity;
import edu.kit.scc.webreg.entity.audit.AuditUserEntity;
import edu.kit.scc.webreg.entity.oidc.OidcUserEntity;
import edu.kit.scc.webreg.exc.UserUpdateException;
import edu.kit.scc.webreg.model.GenericLazyDataModelImpl;
import edu.kit.scc.webreg.service.ASUserAttrService;
......@@ -48,6 +48,7 @@ import edu.kit.scc.webreg.service.GroupService;
import edu.kit.scc.webreg.service.RegistryService;
import edu.kit.scc.webreg.service.RoleService;
import edu.kit.scc.webreg.service.UserService;
import edu.kit.scc.webreg.service.oidc.client.OidcUserService;
import edu.kit.scc.webreg.session.SessionManager;
@ManagedBean
......@@ -62,6 +63,9 @@ public class ShowUserBean implements Serializable {
@Inject
private UserService userService;
@Inject
private OidcUserService oidcUserService;
@Inject
private RoleService roleService;
......@@ -148,6 +152,28 @@ public class ShowUserBean implements Serializable {
}
}
public void updateFromOp() {
user = userService.findByIdWithAll(user.getId());
logger.info("Trying user update for {}", user.getEppn());
if (user instanceof OidcUserEntity) {
try {
oidcUserService.updateUserFromOp((OidcUserEntity) user, "identity-" + sessionManager.getIdentityId());
} catch (UserUpdateException e) {
logger.info("Exception while Querying IDP: {}", e.getMessage());
if (e.getCause() != null) {
logger.info("Cause is: {}", e.getCause().getMessage());
if (e.getCause().getCause() != null) {
logger.info("Inner Cause is: {}", e.getCause().getCause().getMessage());
}
}
}
}
else {
logger.info("No update method available for class {}", user.getClass().getName());
}
}
public void checkAllRegistries() {
user = userService.findByIdWithAll(user.getId());
logger.info("Trying to check all registries for user {}", user.getEppn());
......
......@@ -118,9 +118,9 @@
<h:outputText value="#{showUserBean.entity.attributeStore.get(key)}" />
</p:column>
</p:dataTable>
<div style="margin-top: 12px;">
<p:panel style="margin-top: 12px;" rendered="#{showUserBean.entity.class.simpleName eq 'SamlUserEntity'}">
<p:commandButton value="#{messages.attr_query}" action="#{showUserBean.updateFromIdp()}" update="@form" />
<p:ajaxStatus id="ajaxStatusPanel" style="display: inline-block; margin-left: 8px; vertical-align: baseline;">
<p:ajaxStatus id="ajaxStatusPanelSaml" style="display: inline-block; margin-left: 8px; vertical-align: baseline;">
<f:facet name="start">
<h:graphicImage value="#{resource['/img/ajax-loader.gif']}" alt="#{messages.loading}"/>
</f:facet>
......@@ -128,7 +128,18 @@
<h:outputText value="" />
</f:facet>
</p:ajaxStatus>
</div>
</p:panel>
<p:panel style="margin-top: 12px;" rendered="#{showUserBean.entity.class.simpleName eq 'OidcUserEntity'}">
<p:commandButton value="#{messages.attr_query}" action="#{showUserBean.updateFromOp()}" update="@form" />
<p:ajaxStatus id="ajaxStatusPanelOidc" style="display: inline-block; margin-left: 8px; vertical-align: baseline;">
<f:facet name="start">
<h:graphicImage value="#{resource['/img/ajax-loader.gif']}" alt="#{messages.loading}"/>
</f:facet>
<f:facet name="complete">
<h:outputText value="" />
</f:facet>
</p:ajaxStatus>
</p:panel>
</p:tab>
<p:tab id="tab4" title="#{messages.registered_services}">
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment