/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
*******************************************************************************/
package org.cloudfoundry.identity.uaa.oauth;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.approval.Approval;
import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus;
import org.cloudfoundry.identity.uaa.approval.ApprovalStore;
import org.cloudfoundry.identity.uaa.audit.event.TokenIssuedEvent;
import org.cloudfoundry.identity.uaa.authentication.Origin;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt;
import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper;
import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants;
import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableToken;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableTokenProvisioning;
import org.cloudfoundry.identity.uaa.oauth.token.TokenConstants;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.TokenValidation;
import org.cloudfoundry.identity.uaa.util.UaaTokenUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.TokenPolicy;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import static java.util.Collections.emptySet;
import static java.util.Optional.ofNullable;
import static org.cloudfoundry.identity.uaa.oauth.client.ClientConstants.REQUIRED_USER_GROUPS;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ACR;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ADDITIONAL_AZ_ATTR;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AMR;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AUD;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AUTHORITIES;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AUTH_TIME;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AZP;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.CID;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.CLIENT_ID;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EMAIL;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EXP;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EXTERNAL_ATTR;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.FAMILY_NAME;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.GIVEN_NAME;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.GRANT_TYPE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.IAT;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ISS;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.JTI;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.NONCE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ORIGIN;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.PHONE_NUMBER;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.PREVIOUS_LOGON_TIME;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.PROFILE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.REVOCABLE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.REVOCATION_SIGNATURE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ROLES;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SCOPE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SUB;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ATTRIBUTES;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ID;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_NAME;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ZONE_ID;
import static org.cloudfoundry.identity.uaa.oauth.token.RevocableToken.TokenFormat.JWT;
import static org.cloudfoundry.identity.uaa.oauth.token.RevocableToken.TokenFormat.OPAQUE;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_REFRESH_TOKEN;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_SAML2_BEARER;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_USER_TOKEN;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.REFRESH_TOKEN_SUFFIX;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.REQUEST_TOKEN_FORMAT;
import static org.cloudfoundry.identity.uaa.util.TokenValidation.validate;
import static org.springframework.util.StringUtils.hasText;
/**
* This class provides token services for the UAA. It handles the production and
* consumption of UAA tokens.
*
*/
public class UaaTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
InitializingBean, ApplicationEventPublisherAware {
public static final String UAA_REFRESH_TOKEN = "uaa.offline_token";
private final Log logger = LogFactory.getLog(getClass());
private UaaUserDatabase userDatabase = null;
private ClientDetailsService clientDetailsService = null;
private String issuer = null;
private Set<String> defaultUserAuthorities = new HashSet<String>();
private ApprovalStore approvalStore = null;
private ApplicationEventPublisher applicationEventPublisher;
private List<String> validIdTokenScopes = Arrays.asList("openid");
private TokenPolicy tokenPolicy;
private RevocableTokenProvisioning tokenProvisioning;
private Set<String> excludedClaims = Collections.EMPTY_SET;
private boolean restrictRefreshGrant;
private UaaTokenEnhancer uaaTokenEnhancer = null;
public Set<String> getExcludedClaims() {
return excludedClaims;
}
public void setExcludedClaims(Set<String> excludedClaims) {
this.excludedClaims = excludedClaims;
}
public void setValidIdTokenScopes(List<String> validIdTokenScopes) {
this.validIdTokenScopes = validIdTokenScopes;
}
public RevocableTokenProvisioning getTokenProvisioning() {
return tokenProvisioning;
}
public void setTokenProvisioning(RevocableTokenProvisioning tokenProvisioning) {
this.tokenProvisioning = tokenProvisioning;
}
public void setUaaTokenEnhancer(UaaTokenEnhancer uaaTokenEnhancer) {
this.uaaTokenEnhancer = uaaTokenEnhancer;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest request) throws AuthenticationException {
if (null == refreshTokenValue) {
throw new InvalidTokenException("Invalid refresh token (empty token)");
}
if (!"refresh_token".equals(request.getRequestParameters().get("grant_type"))) {
throw new InvalidGrantException("Invalid grant type: "
+ request.getRequestParameters().get("grant_type"));
}
TokenValidation tokenValidation = validateToken(refreshTokenValue);
Map<String, Object> claims = tokenValidation.getClaims();
refreshTokenValue = tokenValidation.getJwt().getEncoded();
@SuppressWarnings("unchecked")
ArrayList<String> tokenScopes = (ArrayList<String>) claims.get(SCOPE);
if (isRestrictRefreshGrant() && !tokenScopes.contains(UAA_REFRESH_TOKEN)) {
throw new InsufficientScopeException(String.format("Expected scope %s is missing", UAA_REFRESH_TOKEN));
}
// TODO: Should reuse the access token you get after the first
// successful authentication.
// You will get an invalid_grant error if your previous token has not
// expired yet.
// OAuth2RefreshToken refreshToken =
// tokenStore.readRefreshToken(refreshTokenValue);
// if (refreshToken == null) {
// throw new InvalidGrantException("Invalid refresh token: " +
// refreshTokenValue);
// }
String clientId = (String) claims.get(CID);
if (clientId == null || !clientId.equals(request.getClientId())) {
throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);
}
String userid = (String) claims.get(USER_ID);
String refreshTokenId = (String) claims.get(JTI);
String accessTokenId = generateUniqueTokenId();
boolean opaque = TokenConstants.OPAQUE.equals(request.getRequestParameters().get(TokenConstants.REQUEST_TOKEN_FORMAT));
boolean revocable = opaque || (claims.get(REVOCABLE) == null ? false : (Boolean)claims.get(REVOCABLE));
// TODO: Need to add a lookup by id so that the refresh token does not
// need to contain a name
UaaUser user = userDatabase.retrieveUserById(userid);
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
Integer refreshTokenIssuedAt = (Integer) claims.get(IAT);
long refreshTokenIssueDate = refreshTokenIssuedAt.longValue() * 1000l;
Integer refreshTokenExpiry = (Integer) claims.get(EXP);
long refreshTokenExpireDate = refreshTokenExpiry.longValue() * 1000l;
if (new Date(refreshTokenExpireDate).before(new Date())) {
throw new InvalidTokenException("Invalid refresh token (expired): " + refreshTokenValue + " expired at "
+ new Date(refreshTokenExpireDate));
}
// default request scopes to what is in the refresh token
Set<String> requestedScopes = request.getScope();
if (requestedScopes.isEmpty()) {
requestedScopes = new HashSet<>(tokenScopes);
}
// The user may not request scopes that were not part of the refresh
// token
if (tokenScopes.isEmpty() || !tokenScopes.containsAll(requestedScopes)) {
throw new InvalidScopeException("Unable to narrow the scope of the client authentication to "
+ requestedScopes + ".", new HashSet<>(tokenScopes));
}
// from this point on, we only care about the scopes requested, not what
// is in the refresh token
// ensure all requested scopes are approved: either automatically or
// explicitly by the user
String grantType = claims.get(GRANT_TYPE).toString();
checkForApproval(userid, clientId, requestedScopes,
getAutoApprovedScopes(grantType, tokenScopes, client)
);
// if we have reached so far, issue an access token
Integer validity = client.getAccessTokenValiditySeconds();
String nonce = (String) claims.get(NONCE);
@SuppressWarnings("unchecked")
Map<String, String> additionalAuthorizationInfo = (Map<String, String>) claims.get(ADDITIONAL_AZ_ATTR);
@SuppressWarnings("unchecked")
Map<String, String> externalAttributes = (Map<String, String>) claims.get(EXTERNAL_ATTR);
String revocableHashSignature = (String)claims.get(REVOCATION_SIGNATURE);
if (hasText(revocableHashSignature)) {
String clientSecretForHash = client.getClientSecret();
if(clientSecretForHash != null && clientSecretForHash.split(" ").length > 1){
clientSecretForHash = clientSecretForHash.split(" ")[1];
}
String newRevocableHashSignature = UaaTokenUtils.getRevocableTokenSignature(client, clientSecretForHash, user);
if (!revocableHashSignature.equals(newRevocableHashSignature)) {
throw new TokenRevokedException(refreshTokenValue);
}
}
Set<String> audience = new HashSet<>((ArrayList<String>)claims.get(AUD));
int zoneAccessTokenValidity = getZoneAccessTokenValidity();
CompositeAccessToken accessToken =
createAccessToken(
accessTokenId,
user.getId(),
user,
(claims.get(AUTH_TIME) != null) ? new Date(((Long) claims.get(AUTH_TIME)) * 1000l) : null,
validity != null ? validity.intValue() : zoneAccessTokenValidity,
null,
requestedScopes,
clientId,
audience /*request.createOAuth2Request(client).getResourceIds()*/,
grantType,
refreshTokenValue,
nonce,
additionalAuthorizationInfo,
externalAttributes,
new HashSet<>(),
revocableHashSignature,
false,
null, //TODO populate response types
null,
revocable,
null,
null);
DefaultExpiringOAuth2RefreshToken expiringRefreshToken = new DefaultExpiringOAuth2RefreshToken(refreshTokenValue, new Date(refreshTokenExpireDate));
return persistRevocableToken(accessTokenId, refreshTokenId, accessToken, expiringRefreshToken, clientId, user.getId(), opaque, revocable);
}
private int getZoneAccessTokenValidity() {
IdentityZone zone = IdentityZoneHolder.get();
IdentityZoneConfiguration definition = zone.getConfig();
int zoneAccessTokenValidity = getTokenPolicy().getAccessTokenValidity();
if (definition != null) {
zoneAccessTokenValidity = (definition.getTokenPolicy().getAccessTokenValidity() != -1) ? definition.getTokenPolicy().getAccessTokenValidity() : getTokenPolicy().getAccessTokenValidity();
}
return zoneAccessTokenValidity;
}
private void checkForApproval(String userid,
String clientId,
Collection<String> requestedScopes,
Collection<String> autoApprovedScopes) {
if(autoApprovedScopes.containsAll(requestedScopes)) { return; }
Set<String> approvedScopes = new HashSet<>(autoApprovedScopes);
// Search through the users approvals for scopes that are requested, not
// auto approved, not expired,
// not DENIED and not approved more recently than when this access token
// was issued.
List<Approval> approvals = approvalStore.getApprovals(userid, clientId);
for (Approval approval : approvals) {
if (requestedScopes.contains(approval.getScope()) && approval.getStatus() == ApprovalStatus.APPROVED) {
if (!approval.isCurrentlyActive()) {
logger.debug("Approval " + approval + " has expired. Need to re-approve.");
throw new InvalidTokenException("Invalid token (approvals expired)");
}
approvedScopes.add(approval.getScope());
}
}
// Only issue the token if all the requested scopes have unexpired
// approvals made before the refresh token was
// issued OR if those scopes are auto approved
if (!approvedScopes.containsAll(requestedScopes)) {
logger.debug("All requested scopes " + requestedScopes + " were not approved " + approvedScopes);
Set<String> unapprovedScopes = new HashSet<String>(requestedScopes);
unapprovedScopes.removeAll(approvedScopes);
throw new InvalidTokenException("Invalid token (some requested scopes are not approved): "
+ unapprovedScopes);
}
}
private CompositeAccessToken createAccessToken(String tokenId,
String userId,
UaaUser user,
Date userAuthenticationTime,
int validitySeconds,
Collection<GrantedAuthority> clientScopes,
Set<String> requestedScopes,
String clientId,
Set<String> resourceIds,
String grantType,
String refreshToken,
String nonce,
Map<String, String> additionalAuthorizationAttributes,
Map<String, String> externalAttributes,
Set<String> responseTypes,
String revocableHashSignature,
boolean forceIdTokenCreation,
Set<String> externalGroupsForIdToken,
Map<String, List<String>> userAttributesForIdToken,
boolean revocable,
Set<String> authenticationMethods,
Set<String> authNContextClassRef) throws AuthenticationException {
CompositeAccessToken accessToken = new CompositeAccessToken(tokenId);
accessToken.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
accessToken.setRefreshToken(refreshToken == null ? null : new DefaultOAuth2RefreshToken(refreshToken));
if (null == requestedScopes || requestedScopes.size() == 0) {
logger.debug("No scopes were granted");
throw new InvalidTokenException("No scopes were granted");
}
accessToken.setScope(requestedScopes);
Map<String, Object> info = new HashMap<String, Object>();
info.put(JTI, accessToken.getValue());
if (null != additionalAuthorizationAttributes) {
info.put(ADDITIONAL_AZ_ATTR, additionalAuthorizationAttributes);
}
if (null != externalAttributes) {
info.put(EXTERNAL_ATTR, externalAttributes);
}
if (nonce != null) {
info.put(NONCE, nonce);
}
accessToken.setAdditionalInformation(info);
String content;
Map<String, ?> jwtAccessToken = createJWTAccessToken(
accessToken,
userId,
user,
userAuthenticationTime,
clientScopes,
requestedScopes,
clientId,
resourceIds,
grantType,
refreshToken,
revocableHashSignature,
revocable
);
try {
content = JsonUtils.writeValueAsString(jwtAccessToken);
} catch (JsonUtils.JsonUtilException e) {
throw new IllegalStateException("Cannot convert access token to JSON", e);
}
String token = JwtHelper.encode(content, KeyInfo.getActiveKey().getSigner()).getEncoded();
// This setter copies the value and returns. Don't change.
accessToken.setValue(token);
populateIdToken(accessToken,
jwtAccessToken,
requestedScopes,
responseTypes,
clientId,
forceIdTokenCreation,
externalGroupsForIdToken,
user,
userAttributesForIdToken,
authenticationMethods,
authNContextClassRef);
publish(new TokenIssuedEvent(accessToken, SecurityContextHolder.getContext().getAuthentication()));
return accessToken;
}
private void populateIdToken(CompositeAccessToken token,
Map<String, ?> accessTokenValues,
Set<String> scopes,
Set<String> responseTypes,
String aud,
boolean forceIdTokenCreation,
Set<String> externalGroupsForIdToken,
UaaUser user,
Map<String, List<String>> userAttributesForIdToken,
Set<String> authenticationMethods,
Set<String> authNContextClassRef) {
if (forceIdTokenCreation || (scopes.contains("openid") && responseTypes.contains(CompositeAccessToken.ID_TOKEN))) {
try {
Map<String, Object> clone = new HashMap<>(accessTokenValues);
clone.remove(AUTHORITIES);
Set<String> idTokenScopes = new HashSet<>();
for (String sc : scopes) {
if (validIdTokenScopes!=null && validIdTokenScopes.contains(sc)) {
idTokenScopes.add(sc);
}
}
if (authenticationMethods != null) {
clone.put(AMR, authenticationMethods);
}
if (authNContextClassRef != null && authNContextClassRef.size() > 0) {
Map<String, Set<String>> acrValues = new HashMap<>();
acrValues.put("values", authNContextClassRef);
clone.put(ACR, acrValues);
}
clone.put(SCOPE, idTokenScopes);
clone.put(PREVIOUS_LOGON_TIME, user.getPreviousLogonTime());
clone.put(AUD, new HashSet(Arrays.asList(aud)));
if (scopes.contains(ROLES) && (externalGroupsForIdToken != null && !externalGroupsForIdToken.isEmpty())) {
clone.put(ROLES, externalGroupsForIdToken);
}
if (scopes.contains(USER_ATTRIBUTES) && userAttributesForIdToken!=null ) {
clone.put(USER_ATTRIBUTES, userAttributesForIdToken);
}
if(scopes.contains(PROFILE) && user != null) {
String givenName = user.getGivenName();
if(givenName != null) clone.put(GIVEN_NAME, givenName);
String familyName = user.getFamilyName();
if(familyName != null) clone.put(FAMILY_NAME, familyName);
String phoneNumber = user.getPhoneNumber();
if(phoneNumber != null) clone.put(PHONE_NUMBER, phoneNumber);
}
String content = JsonUtils.writeValueAsString(clone);
String encoded = JwtHelper.encode(content, KeyInfo.getActiveKey().getSigner()).getEncoded();
token.setIdTokenValue(encoded);
} catch (JsonUtils.JsonUtilException e) {
throw new IllegalStateException("Cannot convert ID token to JSON", e);
}
}
}
private Map<String, ?> createJWTAccessToken(OAuth2AccessToken token,
String userId,
UaaUser user,
Date userAuthenticationTime,
Collection<GrantedAuthority> clientScopes,
Set<String> requestedScopes,
String clientId,
Set<String> resourceIds,
String grantType,
String refreshToken,
String revocableHashSignature,
boolean revocable) {
Map<String, Object> response = new LinkedHashMap<String, Object>();
response.put(JTI, token.getAdditionalInformation().get(JTI));
response.putAll(token.getAdditionalInformation());
response.put(SUB, clientId);
if (null != clientScopes) {
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(clientScopes));
}
response.put(OAuth2AccessToken.SCOPE, requestedScopes);
response.put(CLIENT_ID, clientId);
response.put(CID, clientId);
response.put(AZP, clientId); //openId Connect
if (revocable) {
response.put(REVOCABLE, true);
}
if (null != grantType) {
response.put(GRANT_TYPE, grantType);
}
if (user!=null && userId!=null) {
response.put(USER_ID, userId);
String origin = user.getOrigin();
if (StringUtils.hasLength(origin)) {
response.put(ORIGIN, origin);
}
String username = user.getUsername();
response.put(USER_NAME, username == null ? userId : username);
String userEmail = user.getEmail();
if (userEmail != null) {
response.put(EMAIL, userEmail);
}
if (userAuthenticationTime!=null) {
response.put(AUTH_TIME, userAuthenticationTime.getTime() / 1000);
}
response.put(SUB, userId);
}
if (StringUtils.hasText(revocableHashSignature)) {
response.put(REVOCATION_SIGNATURE, revocableHashSignature);
}
response.put(IAT, System.currentTimeMillis() / 1000);
response.put(EXP, token.getExpiration().getTime() / 1000);
if (getTokenEndpoint() != null) {
response.put(ISS, getTokenEndpoint());
response.put(ZONE_ID,IdentityZoneHolder.get().getId());
}
// TODO: different values for audience in the AT and RT. Need to sync
// them up
response.put(AUD, resourceIds);
for (String excludedClaim : getExcludedClaims()) {
response.remove(excludedClaim);
}
return response;
}
@Override
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
String userId = null;
Date userAuthenticationTime = null;
UaaUser user = null;
boolean wasIdTokenRequestedThroughAuthCodeScopeParameter = false;
Set<String> authenticationMethods = null;
Set<String> authNContextClassRef = null;
ClientDetails client = clientDetailsService.loadClientByClientId(authentication.getOAuth2Request().getClientId());
Collection<GrantedAuthority> clientScopes = null;
// Clients should really by different kinds of users
if (authentication.isClientOnly()) {
clientScopes = client.getAuthorities();
} else {
userId = getUserId(authentication);
user = userDatabase.retrieveUserById(userId);
if (authentication.getUserAuthentication() instanceof UaaAuthentication) {
userAuthenticationTime = new Date(((UaaAuthentication)authentication.getUserAuthentication()).getAuthenticatedTime());
authenticationMethods = ((UaaAuthentication) authentication.getUserAuthentication()).getAuthenticationMethods();
authNContextClassRef = ((UaaAuthentication) authentication.getUserAuthentication()).getAuthContextClassRef();
}
validateRequiredUserGroups(user, client);
}
String clientSecretForHash = client.getClientSecret();
if(clientSecretForHash != null && clientSecretForHash.split(" ").length > 1){
clientSecretForHash = clientSecretForHash.split(" ")[1];
}
String revocableHashSignature = UaaTokenUtils.getRevocableTokenSignature(client, clientSecretForHash, user);
String tokenId = generateUniqueTokenId();
String refreshTokenId = generateUniqueTokenId() + REFRESH_TOKEN_SUFFIX;
boolean opaque = opaqueTokenRequired(authentication);
boolean accessTokenRevocable = opaque || IdentityZoneHolder.get().getConfig().getTokenPolicy().isJwtRevocable();
boolean refreshTokenRevocable = accessTokenRevocable || TokenConstants.TokenFormat.OPAQUE.getStringValue().equals(IdentityZoneHolder.get().getConfig().getTokenPolicy().getRefreshTokenFormat());
OAuth2RefreshToken refreshToken = null;
if(client.getAuthorizedGrantTypes().contains(GRANT_TYPE_REFRESH_TOKEN)){
refreshToken = createRefreshToken(refreshTokenId, authentication, revocableHashSignature, refreshTokenRevocable);
}
String clientId = authentication.getOAuth2Request().getClientId();
Set<String> userScopes = authentication.getOAuth2Request().getScope();
String grantType = authentication.getOAuth2Request().getRequestParameters().get("grant_type");
Set<String> modifiableUserScopes = new LinkedHashSet<>(userScopes);
Set<String> externalGroupsForIdToken = Collections.EMPTY_SET;
Map<String,List<String>> userAttributesForIdToken = Collections.EMPTY_MAP;
if (authentication.getUserAuthentication() instanceof UaaAuthentication) {
externalGroupsForIdToken = ((UaaAuthentication)authentication.getUserAuthentication()).getExternalGroups();
userAttributesForIdToken = ((UaaAuthentication)authentication.getUserAuthentication()).getUserAttributes();
}
String nonce = authentication.getOAuth2Request().getRequestParameters().get(NONCE);
Map<String, String> additionalAuthorizationAttributes =
getAdditionalAuthorizationAttributes(
authentication.getOAuth2Request().getRequestParameters().get("authorities")
);
if ("authorization_code".equals(authentication.getOAuth2Request().getRequestParameters().get(OAuth2Utils.GRANT_TYPE)) &&
"code".equals(authentication.getOAuth2Request().getRequestParameters().get(OAuth2Utils.RESPONSE_TYPE)) &&
authentication.getOAuth2Request().getRequestParameters().get(OAuth2Utils.SCOPE)!=null &&
authentication.getOAuth2Request().getRequestParameters().get(OAuth2Utils.SCOPE).contains("openid")) {
wasIdTokenRequestedThroughAuthCodeScopeParameter = true;
}
int zoneAccessTokenValidity = getZoneAccessTokenValidity();
Integer validity = client.getAccessTokenValiditySeconds();
Set<String> responseTypes = extractResponseTypes(authentication);
Map<String,String> externalAttributes = null;
if (uaaTokenEnhancer != null) {
externalAttributes = uaaTokenEnhancer.getExternalAttributes(authentication);
}
CompositeAccessToken accessToken =
createAccessToken(
tokenId,
userId,
user,
userAuthenticationTime,
validity != null ? validity.intValue() : zoneAccessTokenValidity,
clientScopes,
modifiableUserScopes,
clientId,
authentication.getOAuth2Request().getResourceIds(),
grantType,
refreshToken != null ? refreshToken.getValue() : null,
nonce,
additionalAuthorizationAttributes,
externalAttributes,
responseTypes,
revocableHashSignature,
wasIdTokenRequestedThroughAuthCodeScopeParameter,
externalGroupsForIdToken,
userAttributesForIdToken,
accessTokenRevocable,
authenticationMethods,
authNContextClassRef);
return persistRevocableToken(tokenId, refreshTokenId, accessToken, refreshToken, clientId, userId, opaque, accessTokenRevocable);
}
public static void validateRequiredUserGroups(UaaUser user, ClientDetails client) {
Collection<String> requiredUserGroups = ofNullable((Collection<String>) client.getAdditionalInformation().get(REQUIRED_USER_GROUPS)).orElse(emptySet());
if (!UaaTokenUtils.hasRequiredUserAuthorities(requiredUserGroups, user.getAuthorities())) {
throw new InvalidTokenException("User does not meet the client's required group criteria.");
}
}
public CompositeAccessToken persistRevocableToken(String tokenId,
String refreshTokenId,
CompositeAccessToken token,
OAuth2RefreshToken refreshToken,
String clientId,
String userId,
boolean opaque,
boolean revocable) {
String scope = token.getScope().toString();
long now = System.currentTimeMillis();
if (revocable) {
RevocableToken revocableAccessToken = new RevocableToken()
.setTokenId(tokenId)
.setClientId(clientId)
.setExpiresAt(token.getExpiration().getTime())
.setIssuedAt(now)
.setFormat(opaque ? OPAQUE.name() : JWT.name())
.setResponseType(RevocableToken.TokenType.ACCESS_TOKEN)
.setZoneId(IdentityZoneHolder.get().getId())
.setUserId(userId)
.setScope(scope)
.setValue(token.getValue());
try {
tokenProvisioning.create(revocableAccessToken);
} catch (DuplicateKeyException updateInstead) {
//TODO this is an uninteded side effect of reusing access token IDs
tokenProvisioning.update(tokenId, revocableAccessToken);
}
}
boolean refreshTokenOpaque = opaque || TokenConstants.TokenFormat.OPAQUE.getStringValue().equals(IdentityZoneHolder.get().getConfig().getTokenPolicy().getRefreshTokenFormat());
boolean refreshTokenRevocable = refreshTokenOpaque || IdentityZoneHolder.get().getConfig().getTokenPolicy().isJwtRevocable();
boolean refreshTokenUnique = IdentityZoneHolder.get().getConfig().getTokenPolicy().isRefreshTokenUnique();
if (refreshToken!=null && refreshTokenRevocable) {
RevocableToken revocableRefreshToken = new RevocableToken()
.setTokenId(refreshTokenId)
.setClientId(clientId)
.setExpiresAt(((ExpiringOAuth2RefreshToken) refreshToken).getExpiration().getTime())
.setIssuedAt(now)
.setFormat(refreshTokenOpaque ? OPAQUE.name() : JWT.name())
.setResponseType(RevocableToken.TokenType.REFRESH_TOKEN)
.setZoneId(IdentityZoneHolder.get().getId())
.setUserId(userId)
.setScope(scope)
.setValue(refreshToken.getValue());
try {
if(refreshTokenUnique) {
tokenProvisioning.deleteRefreshTokensForClientAndUserId(clientId, userId);
}
tokenProvisioning.create(revocableRefreshToken);
} catch (DuplicateKeyException ignore) {
//no need to store refresh tokens again
}
}
CompositeAccessToken result = new CompositeAccessToken(opaque ? tokenId : token.getValue());
result.setIdTokenValue(token.getIdTokenValue());
result.setExpiration(token.getExpiration());
result.setAdditionalInformation(token.getAdditionalInformation());
result.setScope(token.getScope());
result.setTokenType(token.getTokenType());
result.setRefreshToken(refreshToken==null ? null : new DefaultOAuth2RefreshToken(refreshTokenOpaque ? refreshTokenId : refreshToken.getValue()));
return result;
}
protected boolean opaqueTokenRequired(OAuth2Authentication authentication) {
Map<String, String> parameters = authentication.getOAuth2Request().getRequestParameters();
return TokenConstants.OPAQUE.equals(parameters.get(REQUEST_TOKEN_FORMAT)) ||
GRANT_TYPE_USER_TOKEN.equals(parameters.get(GRANT_TYPE));
}
/**
* If an only if the stored request has response_type=code AND
* the request parameters override it using another response_type parameter
* this method will return the requested response_type rather than the stored
* @param authentication
* @return
*/
protected Set<String> extractResponseTypes(OAuth2Authentication authentication) {
Set<String> responseTypes = authentication.getOAuth2Request().getResponseTypes();
if (responseTypes!=null && responseTypes.size()==1) {
String storedResponseType = responseTypes.iterator().next();
String requesedResponseType = authentication.getOAuth2Request().getRequestParameters().get(OAuth2Utils.RESPONSE_TYPE);
if ("code".equals(storedResponseType) &&
requesedResponseType!=null) {
responseTypes = OAuth2Utils.parseParameterList(requesedResponseType);
}
}
return responseTypes;
}
/**
* This method searches the authorities in the request for
* additionalAuthorizationAttributes
* and returns a map of these attributes that will later be added to the
* token
*
* @param authoritiesJson
* @return
*/
private Map<String, String> getAdditionalAuthorizationAttributes(String authoritiesJson) {
if (StringUtils.hasLength(authoritiesJson)) {
try {
@SuppressWarnings("unchecked")
Map<String, Object> authorities = JsonUtils.readValue(authoritiesJson, new TypeReference<Map<String, Object>>() {});
@SuppressWarnings("unchecked")
Map<String, String> additionalAuthorizationAttributes =
(Map<String, String>) authorities.get("az_attr");
return additionalAuthorizationAttributes;
} catch (Throwable t) {
logger.error("Unable to read additionalAuthorizationAttributes", t);
}
}
return null;
}
private ExpiringOAuth2RefreshToken createRefreshToken(String tokenId,
OAuth2Authentication authentication,
String revocableHashSignature,
boolean revocable) {
String grantType = authentication.getOAuth2Request().getRequestParameters().get("grant_type");
Set<String> scope = authentication.getOAuth2Request().getScope();
if (!isRefreshTokenSupported(grantType, scope)) {
return null;
}
Map<String, String> additionalAuthorizationAttributes = getAdditionalAuthorizationAttributes(authentication
.getOAuth2Request().getRequestParameters().get("authorities"));
int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
ExpiringOAuth2RefreshToken token = new DefaultExpiringOAuth2RefreshToken(tokenId,
new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
String userId = getUserId(authentication);
UaaUser user = userDatabase.retrieveUserById(userId);
Map<String,String> externalAttributes = null;
if (uaaTokenEnhancer != null) {
externalAttributes = uaaTokenEnhancer.getExternalAttributes(authentication);
}
String content;
try {
content = JsonUtils.writeValueAsString(
createJWTRefreshToken(
token,
tokenId,
user,
authentication.getOAuth2Request().getScope(),
authentication.getOAuth2Request().getClientId(),
grantType,
additionalAuthorizationAttributes,authentication.getOAuth2Request().getResourceIds(),
revocableHashSignature,
revocable,
externalAttributes
)
);
} catch (JsonUtils.JsonUtilException e) {
throw new IllegalStateException("Cannot convert access token to JSON", e);
}
String jwtToken = JwtHelper.encode(content, KeyInfo.getActiveKey().getSigner()).getEncoded();
ExpiringOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken(jwtToken, token.getExpiration());
return refreshToken;
}
protected String getUserId(OAuth2Authentication authentication) {
return Origin.getUserId(authentication.getUserAuthentication());
}
private Map<String, ?> createJWTRefreshToken(
OAuth2RefreshToken token,
String tokenId,
UaaUser user,
Set<String> scopes,
String clientId,
String grantType,
Map<String, String> additionalAuthorizationAttributes,
Set<String> resourceIds,
String revocableSignature,
boolean revocable,
Map<String, String> externalAttributes) {
Map<String, Object> response = new LinkedHashMap<String, Object>();
response.put(JTI, tokenId);
response.put(SUB, user.getId());
response.put(SCOPE, scopes);
if (null != additionalAuthorizationAttributes) {
response.put(ADDITIONAL_AZ_ATTR, additionalAuthorizationAttributes);
}
if (null != externalAttributes) {
response.put(EXTERNAL_ATTR, externalAttributes);
}
response.put(IAT, System.currentTimeMillis() / 1000);
if (((ExpiringOAuth2RefreshToken) token).getExpiration() != null) {
response.put(EXP, ((ExpiringOAuth2RefreshToken) token).getExpiration().getTime() / 1000);
}
response.put(CID, clientId);
response.put(CLIENT_ID, clientId);
if (getTokenEndpoint() != null) {
response.put(ISS, getTokenEndpoint());
response.put(ZONE_ID,IdentityZoneHolder.get().getId());
}
if (revocable) {
response.put(ClaimConstants.REVOCABLE, true);
}
if (null != grantType) {
response.put(GRANT_TYPE, grantType);
}
if (user!=null) {
response.put(USER_NAME, user.getUsername());
response.put(ORIGIN, user.getOrigin());
response.put(USER_ID, user.getId());
}
if (hasText(revocableSignature)) {
response.put(REVOCATION_SIGNATURE, revocableSignature);
}
response.put(AUD, resourceIds);
return response;
}
protected String generateUniqueTokenId() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* Check the current authorization request to indicate whether a refresh
* token should be issued or not.
*
* @param grantType the current grant type
* @param scope
* @return boolean to indicate if refresh token is supported
*/
protected boolean isRefreshTokenSupported(String grantType, Set<String> scope) {
if (!isRestrictRefreshGrant()) {
return "authorization_code".equals(grantType) ||
"password".equals(grantType) ||
GRANT_TYPE_USER_TOKEN.equals(grantType) ||
GRANT_TYPE_REFRESH_TOKEN.equals(grantType) ||
GRANT_TYPE_SAML2_BEARER.equals(grantType);
} else {
return scope.contains(UAA_REFRESH_TOKEN);
}
}
/**
* The refresh token validity period in seconds
*
* @param authorizationRequest the current authorization request
* @return the refresh token validity period in seconds
*/
protected int getRefreshTokenValiditySeconds(OAuth2Request authorizationRequest) {
ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
Integer validity = client.getRefreshTokenValiditySeconds();
if (validity != null) {
return validity;
}
IdentityZone zone = IdentityZoneHolder.get();
IdentityZoneConfiguration definition = zone.getConfig();
int zoneRefreshTokenValidity = getTokenPolicy().getRefreshTokenValidity();
if (definition != null) {
zoneRefreshTokenValidity = (definition.getTokenPolicy().getRefreshTokenValidity() != -1) ? definition.getTokenPolicy().getRefreshTokenValidity() : tokenPolicy.getRefreshTokenValidity();
}
return zoneRefreshTokenValidity;
}
@Override
public void afterPropertiesSet() throws URISyntaxException {
Assert.notNull(clientDetailsService, "clientDetailsService must be set");
Assert.notNull(issuer, "issuer must be set");
Assert.notNull(approvalStore, "approvalStore must be set");
new URI(issuer); //assert the issuer is a valid url at startup.
}
public void setUserDatabase(UaaUserDatabase userDatabase) {
this.userDatabase = userDatabase;
}
@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException {
if (StringUtils.isEmpty(accessToken)) {
throw new InvalidTokenException("Invalid access token value, must be at least 30 characters:"+accessToken);
}
TokenValidation tokenValidation = validateToken(accessToken);
Map<String, Object> claims = tokenValidation.getClaims();
accessToken = tokenValidation.getJwt().getEncoded();
// Check token expiry
Integer expiration = (Integer) claims.get(EXP);
if (expiration != null && new Date(expiration * 1000l).before(new Date())) {
throw new InvalidTokenException("Invalid access token (expired): " + accessToken + " expired at "
+ new Date(expiration * 1000l));
}
@SuppressWarnings("unchecked")
ArrayList<String> scopes = (ArrayList<String>) claims.get(SCOPE);
AuthorizationRequest authorizationRequest = new AuthorizationRequest((String) claims.get(CLIENT_ID),
scopes);
ArrayList<String> rids = (ArrayList<String>) claims.get(AUD);
//TODO - Fix null resource IDs for a client_credentials request to /oauth/token
Set<String> resourceIds = Collections.unmodifiableSet(rids==null?new HashSet<String>():new HashSet<>(rids));
authorizationRequest.setResourceIds(resourceIds);
authorizationRequest.setApproved(true);
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList(StringUtils
.collectionToCommaDelimitedString(defaultUserAuthorities));
if (claims.containsKey("authorities")) {
Object authoritiesFromClaims = claims.get("authorities");
if (authoritiesFromClaims instanceof String) {
authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) authoritiesFromClaims);
}
if (authoritiesFromClaims instanceof Collection) {
authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
.collectionToCommaDelimitedString((Collection<?>) authoritiesFromClaims));
}
}
Authentication userAuthentication = null;
// Is this a user token - minimum info is user_id
if (claims.containsKey(USER_ID)) {
UaaUser user = userDatabase.retrieveUserById((String)claims.get(USER_ID));
UaaPrincipal principal = new UaaPrincipal(user);
userAuthentication = new UaaAuthentication(principal, UaaAuthority.USER_AUTHORITIES, null);
} else {
authorizationRequest.setAuthorities(authorities);
}
OAuth2Authentication authentication = new UaaOauth2Authentication(accessToken,
IdentityZoneHolder.get().getId(),
authorizationRequest.createOAuth2Request(),
userAuthentication);
authentication.setAuthenticated(true);
return authentication;
}
/**
* This method is implemented to support older API calls that assume the
* presence of a token store
*/
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
TokenValidation tokenValidation = validateToken(accessToken);
Map<String, Object> claims = tokenValidation.getClaims();
accessToken = tokenValidation.getJwt().getEncoded();
// Expiry is verified by check_token
CompositeAccessToken token = new CompositeAccessToken(accessToken);
token.setTokenType(OAuth2AccessToken.BEARER_TYPE);
Integer exp = (Integer) claims.get(EXP);
if (null != exp) {
token.setExpiration(new Date(exp.longValue() * 1000l));
}
@SuppressWarnings("unchecked")
ArrayList<String> scopes = (ArrayList<String>) claims.get(SCOPE);
if (null != scopes && scopes.size() > 0) {
token.setScope(new HashSet<>(scopes));
}
String clientId = (String) claims.get(CID);
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
String userId = (String)claims.get(USER_ID);
// Only check user access tokens
if (null != userId) {
@SuppressWarnings("unchecked")
ArrayList<String> tokenScopes = (ArrayList<String>) claims.get(SCOPE);
Set<String> autoApprovedScopes = getAutoApprovedScopes(claims.get(GRANT_TYPE), tokenScopes, client);
checkForApproval(userId, clientId, tokenScopes, autoApprovedScopes);
}
return token;
}
private Set<String> getAutoApprovedScopes(Object grantType, Collection<String> tokenScopes, ClientDetails client) {
// ALL requested scopes are considered auto-approved for password grant
if (grantType != null && "password".equals(grantType.toString())) {
return new HashSet<>(tokenScopes);
}
BaseClientDetails clientDetails = (BaseClientDetails) client;
return UaaTokenUtils.retainAutoApprovedScopes(tokenScopes, clientDetails.getAutoApproveScopes());
}
protected TokenValidation validateToken(String token) {
TokenValidation tokenValidation;
if (!UaaTokenUtils.isJwtToken(token)) {
RevocableToken revocableToken;
try {
revocableToken = tokenProvisioning.retrieve(token);
} catch(EmptyResultDataAccessException ex) {
throw new TokenRevokedException("The token expired, was revoked, or the token ID is incorrect: " + token);
}
token = revocableToken.getValue();
}
tokenValidation = validate(token)
.checkRevocableTokenStore(tokenProvisioning)
.throwIfInvalid();
Jwt tokenJwt = tokenValidation.getJwt();
String keyId = tokenJwt.getHeader().getKid();
KeyInfo key;
if(keyId!=null) {
key = KeyInfo.getKey(keyId);
} else {
key = KeyInfo.getActiveKey();
}
if(key == null) {
throw new InvalidTokenException("Invalid key ID: " + keyId);
}
SignatureVerifier verifier = key.getVerifier();
tokenValidation
.checkSignature(verifier)
.throwIfInvalid()
;
Map<String, Object> claims = tokenValidation.getClaims();
tokenValidation
.checkIssuer(getTokenEndpoint())
.throwIfInvalid()
;
String clientId = (String) claims.get(CID);
String userId = (String) claims.get(USER_ID);
ClientDetails client;
try {
client = clientDetailsService.loadClientByClientId(clientId);
} catch (NoSuchClientException x) {
//happens if the client is deleted and token exist
throw new InvalidTokenException("Invalid client ID "+clientId);
}
UaaUser user = null;
if( UaaTokenUtils.isUserToken(claims)) {
try {
user = userDatabase.retrieveUserById(userId);
} catch (UsernameNotFoundException e) {
throw new InvalidTokenException("Token bears a non-existent user ID: " + userId);
}
}
tokenValidation.checkClientAndUser(client, user).throwIfInvalid();
List<String> clientSecrets = new ArrayList<>();
List<String> revocationSignatureList = new ArrayList<>();
if (client.getClientSecret() != null) {
clientSecrets.addAll(Arrays.asList(client.getClientSecret().split(" ")));
} else {
revocationSignatureList.add(UaaTokenUtils.getRevocableTokenSignature(client, null, user));
}
for (String clientSecret : clientSecrets) {
revocationSignatureList.add(UaaTokenUtils.getRevocableTokenSignature(client, clientSecret, user));
}
tokenValidation = tokenValidation.checkRevocationSignature(revocationSignatureList);
tokenValidation.throwIfInvalid();
return tokenValidation;
}
/**
* This method is implemented only to support older API calls that assume
* the presence of a token store
*/
@Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
return null;
}
public void setIssuer(String issuer) throws URISyntaxException {
Assert.notNull(issuer);
UaaTokenUtils.constructTokenEndpointUrl(issuer);
this.issuer = issuer;
}
public String getIssuer() {
return issuer;
}
public String getTokenEndpoint() {
try {
return UaaTokenUtils.constructTokenEndpointUrl(issuer);
} catch (URISyntaxException e) {
logger.error("Failed to get token endpoint for issuer " + issuer, e);
throw new IllegalArgumentException(e);
}
}
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
public void setDefaultUserAuthorities(Set<String> defaultUserAuthorities) {
this.defaultUserAuthorities = defaultUserAuthorities;
}
public void setApprovalStore(ApprovalStore approvalStore) {
this.approvalStore = approvalStore;
}
private void publish(TokenIssuedEvent event) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(event);
}
}
public void setTokenPolicy(TokenPolicy tokenPolicy) {
this.tokenPolicy = tokenPolicy;
}
public TokenPolicy getTokenPolicy() {
return tokenPolicy;
}
public boolean isRestrictRefreshGrant() {
return restrictRefreshGrant;
}
public void setRestrictRefreshGrant(boolean restrictRefreshGrant) {
this.restrictRefreshGrant = restrictRefreshGrant;
}
}