pl.edu.icm.unity.oauth.as.TrustedOAuthClientsManagement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of unity-server-oauth Show documentation
Show all versions of unity-server-oauth Show documentation
Client and server OAuth support
The newest version!
/*
* Copyright (c) 2021 Bixbit - Krzysztof Benedyczak. All rights reserved.
* See LICENCE.txt file for licensing information.
*/
package pl.edu.icm.unity.oauth.as;
import io.imunity.idp.*;
import io.imunity.idp.IdPClientData.AccessStatus;
import io.imunity.idp.LastIdPClinetAccessAttributeManagement.LastIdPClientAccessKey;
import io.imunity.vaadin.auth.services.idp.IdpUsersHelper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import pl.edu.icm.unity.base.attribute.Attribute;
import pl.edu.icm.unity.base.attribute.AttributeExt;
import pl.edu.icm.unity.base.attribute.image.UnityImage;
import pl.edu.icm.unity.base.endpoint.Endpoint;
import pl.edu.icm.unity.base.entity.EntityParam;
import pl.edu.icm.unity.base.exceptions.EngineException;
import pl.edu.icm.unity.base.exceptions.InternalException;
import pl.edu.icm.unity.base.identity.Identity;
import pl.edu.icm.unity.base.json.JsonUtil;
import pl.edu.icm.unity.base.message.MessageSource;
import pl.edu.icm.unity.engine.api.EndpointManagement;
import pl.edu.icm.unity.engine.api.PreferencesManagement;
import pl.edu.icm.unity.engine.api.attributes.AttributeTypeSupport;
import pl.edu.icm.unity.engine.api.authn.AuthorizationException;
import pl.edu.icm.unity.engine.api.authn.InvocationContext;
import pl.edu.icm.unity.engine.api.authn.LoginSession;
import pl.edu.icm.unity.engine.api.bulk.BulkGroupQueryService;
import pl.edu.icm.unity.engine.api.bulk.EntityInGroupData;
import pl.edu.icm.unity.engine.api.exceptions.RuntimeEngineException;
import pl.edu.icm.unity.engine.api.token.SecuredTokensManagement;
import pl.edu.icm.unity.engine.api.utils.TimeUtil;
import pl.edu.icm.unity.oauth.as.preferences.OAuthPreferences;
import pl.edu.icm.unity.oauth.as.preferences.OAuthPreferences.OAuthClientSettings;
import pl.edu.icm.unity.oauth.as.token.access.OAuthAccessTokenRepository;
import pl.edu.icm.unity.oauth.as.token.access.OAuthRefreshTokenRepository;
import pl.edu.icm.unity.oauth.as.webauthz.OAuthAuthzWebEndpoint;
import pl.edu.icm.unity.stdext.attr.ImageAttributeSyntax;
import pl.edu.icm.unity.stdext.identity.UsernameIdentity;
import io.imunity.vaadin.endpoint.common.consent_utils.URIPresentationHelper;
import java.io.IOException;
import java.io.StringReader;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
@Component
public class TrustedOAuthClientsManagement implements TrustedIdPClientsManagement
{
private final SecuredTokensManagement tokenMan;
private final PreferencesManagement preferencesManagement;
private final OAuthAccessTokenRepository accessTokenDAO;
private final OAuthRefreshTokenRepository refreshTokenDAO;
private final EndpointManagement endpointManagement;
private final AttributeTypeSupport aTypeSupport;
private final BulkGroupQueryService bulkService;
private final IdpUsersHelper idpUsersHelper;
private final MessageSource msg;
private final OAuthScopesService scopesService;
private final LastIdPClinetAccessAttributeManagement lastAccessAttributeManagement;
public TrustedOAuthClientsManagement(SecuredTokensManagement tokenMan, PreferencesManagement preferencesManagement,
OAuthAccessTokenRepository accessTokenDAO, OAuthRefreshTokenRepository refreshTokenDAO, @Qualifier("insecure") EndpointManagement endpointManagement,
@Qualifier("insecure") BulkGroupQueryService bulkService, IdpUsersHelper idpUsersHelper, MessageSource msg,
OAuthScopesService scopesService, AttributeTypeSupport aTypeSupport,
LastIdPClinetAccessAttributeManagement lastAccessAttributeManagement)
{
this.tokenMan = tokenMan;
this.preferencesManagement = preferencesManagement;
this.accessTokenDAO = accessTokenDAO;
this.refreshTokenDAO = refreshTokenDAO;
this.endpointManagement = endpointManagement;
this.bulkService = bulkService;
this.idpUsersHelper = idpUsersHelper;
this.msg = msg;
this.scopesService = scopesService;
this.aTypeSupport = aTypeSupport;
this.lastAccessAttributeManagement = lastAccessAttributeManagement;
}
@Override
public List getIdpClientsData() throws EngineException
{
Map perClientData = getClientPreferencesAndTokensGroupedByClient();
List services = getServices();
List ret = new ArrayList<>();
for (OAuthServiceConfiguration service : services)
{
for (String client : perClientData.keySet())
{
List accessTokens = perClientData.get(client).tokens.stream()
.filter(t -> t.type.equals(OAuthAccessTokenRepository.INTERNAL_ACCESS_TOKEN)
&& t.token.getIssuerUri().equals(service.issuerURI))
.sorted((t1, t2) -> t2.createdTime.compareTo(t1.createdTime)).collect(Collectors.toList());
List refreshTokens = perClientData.get(client).tokens.stream()
.filter(t -> t.type.equals(OAuthRefreshTokenRepository.INTERNAL_REFRESH_TOKEN)
&& t.token.getIssuerUri().equals(service.issuerURI))
.sorted((t1, t2) -> t2.createdTime.compareTo(t1.createdTime)).collect(Collectors.toList());
Optional serviceClientOp = service.clients.stream().filter(c -> c.id.isPresent() && c.id.get().equals(client))
.findAny();
if (serviceClientOp.isPresent())
{
OAuthClientInfo oauAuthClientInfo = serviceClientOp.get();
if (isDisallowed(perClientData.get(client).preferences))
{
ret.add(IdPClientData.builder().withApplicationId(new ApplicationId(client))
.withLogo(oauAuthClientInfo.logo)
.withAccessProtocol(AccessProtocol.OAuth)
.withAccessStatus(AccessStatus.disallowWithoutAsking)
.withApplicationName(oauAuthClientInfo.name)
.withApplicationDomain(oauAuthClientInfo.redirectURI)
.withAccessDeniedTime(Optional.of(perClientData.get(client).preferences.get().getTimestamp()))
.build());
}
if (accessTokens.size() > 0 || refreshTokens.size() > 0
|| isAllowedWithoutAsking(perClientData.get(client).preferences))
{
Set scopes = getScopes(accessTokens, service);
ret.add(IdPClientData.builder().withApplicationId(new ApplicationId(client))
.withLogo(oauAuthClientInfo.logo)
.withAccessProtocol(AccessProtocol.OAuth)
.withLastAccessTime(Optional.ofNullable(getLastAccessByClient()
.get(new LastIdPClientAccessKey(AccessProtocol.OAuth, client))))
.withAccessStatus(isAllowedWithoutAsking(perClientData.get(client).preferences)
? AccessStatus.allowWithoutAsking
: AccessStatus.allow)
.withAccessGrantTime(getGrantTime(refreshTokens, perClientData.get(client).preferences))
.withApplicationName(oauAuthClientInfo.name)
.withAccessScopes(Optional.ofNullable(
scopes.size() > 0 ? scopes.stream().collect(Collectors.toList()) : null))
.withApplicationDomain(oauAuthClientInfo.redirectURI)
.withTechnicalInformations(getTechnicalInformations(accessTokens, refreshTokens))
.build());
}
}
}
}
return ret;
}
private Set getScopes(List accessTokens, OAuthServiceConfiguration service)
{
Set scopes = new HashSet<>();
for (int i = 0; i < accessTokens.size(); i++)
{
scopes.addAll(getScopes(accessTokens.get(i), service));
}
return scopes;
}
private List getTechnicalInformations(List accessTokens,
List refreshTokens)
{
List technicalInformations = new ArrayList<>();
for (int i = 0; i < accessTokens.size(); i++)
{
technicalInformations.add(TechnicalInformationProperty.builder()
.withTitleKey(msg.getMessage("OAuthApplicationProvider.accessTokenLabel")
+ (accessTokens.size() > 1 ? " (" + (i + 1) + "):" : ":"))
.withValue(getTokenRepresentation(accessTokens.get(i).createdTime, accessTokens.get(i).expiredTime,
accessTokens.get(i).token.getAccessToken()))
.build());
}
for (int i = 0; i < refreshTokens.size(); i++)
{
technicalInformations.add(TechnicalInformationProperty.builder()
.withTitleKey(msg.getMessage("OAuthApplicationProvider.refreshTokenLabel")
+ (refreshTokens.size() > 1 ? " (" + (i + 1) + "):" : ":"))
.withValue(getTokenRepresentation(refreshTokens.get(i).createdTime,
refreshTokens.get(i).expiredTime, refreshTokens.get(i).token.getRefreshToken()))
.build());
}
return technicalInformations;
}
private String getTokenRepresentation(Instant createTime, Instant expired, String value)
{
return value + "
"
+ ((createTime != null
? (msg.getMessage("OAuthApplicationProvider.issuedOn") + " "
+ TimeUtil.formatStandardInstant(createTime))
: "")
+ (expired != null
? "
" + msg.getMessage("OAuthApplicationProvider.expiresOn") + " "
+ TimeUtil.formatStandardInstant(expired)
: ""));
}
private Map getLastAccessByClient() throws EngineException
{
return lastAccessAttributeManagement.getLastAccessByClient();
}
private Optional getGrantTime(List refreshTokens,
Optional preferences)
{
if (refreshTokens.isEmpty() && (preferences.isEmpty() || preferences.get().getTimestamp() == null))
{
return Optional.empty();
}
if (preferences.isEmpty() || preferences.get().getTimestamp() == null)
return Optional.of(refreshTokens.get(refreshTokens.size() - 1).createdTime);
if (refreshTokens.isEmpty())
{
return Optional.of(preferences.get().getTimestamp());
}
return Optional.of(
refreshTokens.get(refreshTokens.size() - 1).createdTime.compareTo(preferences.get().getTimestamp()) < 0
? refreshTokens.get(refreshTokens.size() - 1).createdTime
: preferences.get().getTimestamp());
}
private List getScopes(OAuthTokenWithTime accessToken, OAuthServiceConfiguration service)
{
List scopes = new ArrayList<>();
for (String scope : accessToken.token.getEffectiveScope())
{
Optional desc = service.scopes.stream().filter(s -> s.name.equals(scope)).findFirst();
if (desc.isEmpty() || desc.get().description == null)
{
scopes.add(scope);
} else
{
scopes.add(desc.get().description);
}
}
return scopes;
}
private Map getClientPreferencesAndTokensGroupedByClient() throws EngineException
{
Map perClientData = new HashMap<>();
OAuthPreferences preferences = getPreferences();
List tokens = getTokens();
for (String clientPref : preferences.getKeys())
{
if (perClientData.containsKey(clientPref))
perClientData.get(clientPref).setPreferences(preferences.getSPSettings(clientPref));
else
perClientData.put(clientPref, new TokensAndPreferences(preferences.getSPSettings(clientPref)));
}
for (OAuthTokenWithTime token : tokens)
{
if (perClientData.containsKey(token.token.getClientUsername()))
perClientData.get(token.token.getClientUsername()).getTokens().add(token);
else
perClientData.put(token.token.getClientUsername(), new TokensAndPreferences(token));
}
return perClientData;
}
private boolean isDisallowed(Optional preferences)
{
return preferences.isPresent() && !preferences.get().isDefaultAccept() && preferences.get().isDoNotAsk();
}
private boolean isAllowedWithoutAsking(Optional preferences)
{
return preferences.isPresent() && preferences.get().isDefaultAccept() && preferences.get().isDoNotAsk();
}
protected List getTokens() throws EngineException
{
List tokens = new ArrayList<>();
tokens.addAll(accessTokenDAO.getOwnedAccessTokens().stream()
.map(t -> new OAuthTokenWithTime(t.getType(), t.getCreated().toInstant(),
t.getExpires() != null ? t.getExpires().toInstant() : null,
OAuthToken.getInstanceFromJson(t.getContents()), t.getValue()))
.collect(Collectors.toList()));
tokens.addAll(refreshTokenDAO.getOwnedRefreshTokens().stream()
.map(t -> new OAuthTokenWithTime(t.getType(), t.getCreated().toInstant(),
t.getExpires() != null ? t.getExpires().toInstant() : null,
OAuthToken.getInstanceFromJson(t.getContents()), t.getValue()))
.collect(Collectors.toList()));
return tokens;
}
private OAuthPreferences getPreferences() throws EngineException
{
LoginSession entity = InvocationContext.getCurrent().getLoginSession();
EntityParam entityParam = new EntityParam(entity.getEntityId());
String pref = preferencesManagement.getPreference(entityParam, OAuthPreferences.ID);
OAuthPreferences preferences = new OAuthPreferences();
if (pref != null)
preferences.setSerializedConfiguration(JsonUtil.parse(pref));
return preferences;
}
private List getServices() throws AuthorizationException
{
List ret = new ArrayList<>();
for (Endpoint endpoint : endpointManagement.getEndpoints().stream()
.filter(e -> e.getTypeId().equals(OAuthAuthzWebEndpoint.Factory.TYPE.getName()))
.collect(Collectors.toList()))
{
OAuthServiceConfiguration config = new OAuthServiceConfiguration(msg,
endpoint.getConfiguration().getConfiguration(), scopesService);
ret.add(config);
}
return ret;
}
private void clearPreferences(String appId) throws EngineException
{
OAuthPreferences preferences = getPreferences();
preferences.removeSPSettings(appId);
OAuthPreferences.savePreferences(preferencesManagement, preferences);
}
@Override
public synchronized void unblockAccess(ApplicationId appId) throws EngineException
{
clearPreferences(appId.id);
}
@Override
public synchronized void revokeAccess(ApplicationId appId) throws EngineException
{
List tokens = getTokens();
for (OAuthTokenWithTime token : tokens)
{
if (token.token.getClientUsername().equals(appId.id))
{
tokenMan.removeToken(token.type, token.value);
}
}
clearPreferences(appId.id);
}
@Override
public AccessProtocol getSupportedProtocol()
{
return AccessProtocol.OAuth;
}
private static class OAuthTokenWithTime
{
public final OAuthToken token;
public final Instant createdTime;
public final Instant expiredTime;
public final String type;
public final String value;
public OAuthTokenWithTime(String type, Instant createdTime, Instant expiredTime, OAuthToken token, String value)
{
this.type = type;
this.token = token;
this.createdTime = createdTime;
this.expiredTime = expiredTime;
this.value = value;
}
}
private static class OAuthClientInfo
{
public final Optional name;
public final Optional id;
public final Optional redirectURI;
public final Optional logo;
private OAuthClientInfo(Builder builder)
{
this.name = builder.name;
this.id = builder.id;
this.redirectURI = builder.redirectURI;
this.logo = builder.logo;
}
public static Builder builder()
{
return new Builder();
}
public static final class Builder
{
private Optional name = Optional.empty();
private Optional id = Optional.empty();;
private Optional redirectURI = Optional.empty();;
private Optional logo = Optional.empty();;
private Builder()
{
}
public Builder withName(String name)
{
this.name = Optional.ofNullable(name);
return this;
}
public Builder withId(String id)
{
this.id = Optional.ofNullable(id);
return this;
}
public Builder withRedirectURI(String redirectURIs)
{
this.redirectURI = Optional.ofNullable(redirectURIs).map(URIPresentationHelper
::getHumanReadableDomain);
return this;
}
public Builder withLogo(byte[] logo)
{
this.logo = Optional.ofNullable(logo);
return this;
}
public OAuthClientInfo build()
{
return new OAuthClientInfo(this);
}
}
}
private static class TokensAndPreferences
{
private List tokens;
private Optional preferences;
public TokensAndPreferences(OAuthClientSettings clientPref)
{
this.preferences = Optional.of(clientPref);
this.tokens = new ArrayList<>();
}
public TokensAndPreferences(OAuthTokenWithTime token)
{
this.tokens = new ArrayList<>();
this.preferences = Optional.empty();
tokens.add(token);
}
public List getTokens()
{
return tokens;
}
public void setPreferences(OAuthClientSettings preferences)
{
this.preferences = Optional.ofNullable(preferences);
}
}
private class OAuthServiceConfiguration
{
public final String issuerURI;
public final List scopes;
public final List clients;
public OAuthServiceConfiguration(MessageSource msg, String properties, OAuthScopesService scopeService)
{
Properties raw = new Properties();
try
{
raw.load(new StringReader(properties));
} catch (IOException e)
{
throw new InternalException("Invalid configuration of the oauth idp service", e);
}
OAuthASProperties oauthProperties = new OAuthASProperties(raw);
issuerURI = oauthProperties.getIssuerName();
scopes = new ArrayList<>();
scopeService.getScopes(oauthProperties).stream().forEach(s ->
{
scopes.add(s);
});
String clientGroupPath = oauthProperties.getValue(OAuthASProperties.CLIENTS_GROUP);
clients = getOAuthClients(clientGroupPath);
}
private List getOAuthClients(String group)
{
try
{
List clients = new ArrayList<>();
Map membershipInfo = bulkService
.getMembershipInfo(bulkService.getBulkMembershipData(group));
String nameAttr = idpUsersHelper.getClientNameAttr();
for (EntityInGroupData member : membershipInfo.values())
{
if (isOAuthClient(member))
clients.add(getOAuthClient(member, group, nameAttr));
}
return clients;
} catch (EngineException e)
{
throw new RuntimeEngineException(e);
}
}
private boolean isOAuthClient(EntityInGroupData candidate)
{
return candidate.groupAttributesByName.keySet().contains(OAuthSystemAttributesProvider.ALLOWED_FLOWS)
&& getUserName(candidate.entity.getIdentities()) != null;
}
private OAuthClientInfo getOAuthClient(EntityInGroupData info, String group, String nameAttr)
throws EngineException
{
OAuthClientInfo.Builder oauthClientInfoBuilder = OAuthClientInfo.builder();
oauthClientInfoBuilder.withId(getUserName(info.entity.getIdentities()));
Map oauthGroupAttrs = info.groupAttributesByName;
if (oauthGroupAttrs.containsKey(OAuthSystemAttributesProvider.ALLOWED_RETURN_URI))
{
oauthClientInfoBuilder
.withRedirectURI(oauthGroupAttrs.get(OAuthSystemAttributesProvider.ALLOWED_RETURN_URI)
.getValues()
.isEmpty()
? null
: oauthGroupAttrs.get(OAuthSystemAttributesProvider.ALLOWED_RETURN_URI)
.getValues()
.get(0));
}
if (oauthGroupAttrs.containsKey(OAuthSystemAttributesProvider.CLIENT_NAME))
{
Attribute title = oauthGroupAttrs.get(OAuthSystemAttributesProvider.CLIENT_NAME);
if (!title.getValues().isEmpty())
{
oauthClientInfoBuilder.withName(title.getValues().get(0));
}
}
if (oauthClientInfoBuilder.name.isEmpty() && nameAttr != null && info.rootAttributesByName.containsKey(nameAttr)
&& !info.rootAttributesByName.get(nameAttr).getValues().isEmpty())
{
oauthClientInfoBuilder.withName(info.rootAttributesByName.get(nameAttr)
.getValues()
.get(0));
}
if (oauthGroupAttrs.containsKey(OAuthSystemAttributesProvider.CLIENT_LOGO))
{
oauthClientInfoBuilder
.withLogo(getLogo(oauthGroupAttrs.get(OAuthSystemAttributesProvider.CLIENT_LOGO)));
}
return oauthClientInfoBuilder.build();
}
private byte[] getLogo(Attribute logoAttr)
{
if (logoAttr.getValues().isEmpty() || logoAttr.getValues().get(0) == null)
return null;
ImageAttributeSyntax syntax = (ImageAttributeSyntax) aTypeSupport.getSyntax(logoAttr);
UnityImage image = syntax.convertFromString(logoAttr.getValues().get(0));
return image.getImage();
}
private String getUserName(List identities)
{
for (Identity i : identities)
if (i.getTypeId().equals(UsernameIdentity.ID))
return i.getValue();
return null;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy