/** * ============================================================================= * * ORCID (R) Open Source * http://orcid.org * * Copyright (c) 2012-2014 ORCID, Inc. * Licensed under an MIT-Style License (MIT) * http://orcid.org/open-source-license * * This copyright and license information (including a link to the full license) * shall be included in its entirety in all copies or substantial portion of * the software. * * ============================================================================= */ package org.orcid.persistence.jpa.entities; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.persistence.Basic; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Transient; import org.hibernate.annotations.Sort; import org.hibernate.annotations.SortType; import org.orcid.jaxb.model.clientgroup.ClientType; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.util.StringUtils; /** * @author Declan Newman */ @Entity @Table(name = "client_details") public class ClientDetailsEntity extends BaseEntity<String> implements ClientDetails, ProfileAware, Serializable { private static final long serialVersionUID = 1L; // Default is 20 years! public static final int DEFAULT_TOKEN_VALIDITY = 631138519; private String clientId; private ClientType clientType; private String clientName; private String clientDescription; private String clientWebsite; private String clientSecret; private String decryptedClientSecret; private SortedSet<ClientSecretEntity> clientSecrets; private Set<ClientScopeEntity> clientScopes = Collections.emptySet(); private Set<ClientResourceIdEntity> clientResourceIds = Collections.emptySet(); private Set<ClientAuthorisedGrantTypeEntity> clientAuthorizedGrantTypes = Collections.emptySet(); private SortedSet<ClientRedirectUriEntity> clientRegisteredRedirectUris; private List<ClientGrantedAuthorityEntity> clientGrantedAuthorities = Collections.emptyList(); private String groupProfileId; private String authenticationProviderId; private Set<CustomEmailEntity> customEmails = Collections.emptySet(); private int accessTokenValiditySeconds = DEFAULT_TOKEN_VALIDITY; private boolean persistentTokensEnabled = false; private String emailAccessReason; private boolean allowAutoDeprecate = false; public ClientDetailsEntity() { } public ClientDetailsEntity(String clientId) { this.clientId = clientId; } public ClientDetailsEntity(String clientId, String clientName) { this.clientId = clientId; this.clientName = clientName; } /** * This should be implemented by all entity classes to return the id of the * entity represented by the <T> generic argument * * @return the id of the entity */ @Id @Column(name = "client_details_id", length = 150) public String getId() { return clientId; } public void setId(String clientId) { this.clientId = clientId; } @Basic @Enumerated(EnumType.STRING) @Column(name = "client_type") public ClientType getClientType() { return clientType; } public void setClientType(ClientType clientType) { this.clientType = clientType; } @Column(name = "client_name", length = 255) public String getClientName() { return clientName; } public void setClientName(String clientName) { this.clientName = clientName; } @Column(name = "client_description") public String getClientDescription() { return clientDescription; } public void setClientDescription(String clientDescription) { this.clientDescription = clientDescription; } @Column(name = "client_website") public String getClientWebsite() { return clientWebsite; } public void setClientWebsite(String clientWebsite) { this.clientWebsite = clientWebsite; } @OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "clientDetailsEntity", orphanRemoval = true) public Set<ClientScopeEntity> getClientScopes() { return clientScopes; } public void setClientScopes(Set<ClientScopeEntity> clientScopes) { this.clientScopes = clientScopes; } @OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "clientDetailsEntity", orphanRemoval = true) public Set<ClientResourceIdEntity> getClientResourceIds() { return clientResourceIds; } public void setClientResourceIds(Set<ClientResourceIdEntity> clientResourceIds) { this.clientResourceIds = clientResourceIds; } @OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "clientDetailsEntity", orphanRemoval = true) public Set<ClientAuthorisedGrantTypeEntity> getClientAuthorizedGrantTypes() { return clientAuthorizedGrantTypes; } public void setClientAuthorizedGrantTypes(Set<ClientAuthorisedGrantTypeEntity> clientAuthorizedGrantTypes) { this.clientAuthorizedGrantTypes = clientAuthorizedGrantTypes; } @OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "clientDetailsEntity", orphanRemoval = true) @Sort(type = SortType.NATURAL) public SortedSet<ClientRedirectUriEntity> getClientRegisteredRedirectUris() { return clientRegisteredRedirectUris; } public void setClientRegisteredRedirectUris(SortedSet<ClientRedirectUriEntity> clientRegisteredRedirectUris) { this.clientRegisteredRedirectUris = clientRegisteredRedirectUris; } @OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "clientDetailsEntity", orphanRemoval = true) public List<ClientGrantedAuthorityEntity> getClientGrantedAuthorities() { return clientGrantedAuthorities; } public void setClientGrantedAuthorities(List<ClientGrantedAuthorityEntity> clientGrantedAuthorities) { this.clientGrantedAuthorities = clientGrantedAuthorities; } @Column(name = "group_orcid") @JoinColumn(name = "group_orcid") public String getGroupProfileId() { return groupProfileId; } public void setGroupProfileId(String groupProfileId) { this.groupProfileId = groupProfileId; } @Override @Transient public ProfileEntity getProfile() { return new ProfileEntity(this.getGroupProfileId()); } // Below are the overriden ClientDetails methods /** * The client id. This is a transient field (hence no setter). This is * effectively the same as returning the id from {@link #getId()} * * @return The client id. */ @Override @Transient public String getClientId() { return clientId; } /** * The resources that this client can access. Ignored if empty. * * @return The resources of this client. */ @Override @Transient public Set<String> getResourceIds() { Set<String> rids = new HashSet<String>(); if (clientResourceIds != null && !clientResourceIds.isEmpty()) { for (ClientResourceIdEntity resourceIdEntity : clientResourceIds) { rids.add(resourceIdEntity.getResourceId()); } } return rids; } /** * Whether a secret is required to authenticate this client. * * @return Whether a secret is required to authenticate this client. */ @Override @Transient public boolean isSecretRequired() { return StringUtils.hasText(clientSecret); } /** * The client secret. Ignored if the {@link #isSecretRequired() secret isn't * required}. * * @return The client secret. */ @Override @Transient public String getClientSecret() { return getDecryptedClientSecret(); } @OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "clientDetailsEntity", orphanRemoval = true) @Sort(type = SortType.NATURAL) public Set<ClientSecretEntity> getClientSecrets() { return clientSecrets; } public void setClientSecrets(SortedSet<ClientSecretEntity> clientSecrets) { this.clientSecrets = clientSecrets; } @OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "clientDetailsEntity", orphanRemoval = true) public Set<CustomEmailEntity> getCustomEmails() { return customEmails; } public void setCustomEmails(Set<CustomEmailEntity> customEmails) { this.customEmails = customEmails; } @Column(name = "persistent_tokens_enabled") public boolean isPersistentTokensEnabled() { return persistentTokensEnabled; } public void setPersistentTokensEnabled(boolean persistentTokensEnabled) { this.persistentTokensEnabled = persistentTokensEnabled; } /** * Reason, if any, client wants to access users' private email addresses. * * @return reason client wants to know private email addresses */ @Column(name = "email_access_reason", length = 500) public String getEmailAccessReason() { return emailAccessReason; } public void setEmailAccessReason(String emailAccessReason) { this.emailAccessReason = emailAccessReason; } @Transient public String getClientSecretForJpa() { if (clientSecrets == null || clientSecrets.isEmpty()) { return null; } return clientSecrets.first().getClientSecret(); } public void setClientSecretForJpa(String clientSecret) { if (clientSecrets == null) { clientSecrets = new TreeSet<>(); } clientSecrets.add(new ClientSecretEntity(clientSecret, this)); } public void setClientSecretForJpa(String clientSecret, boolean primary) { if (clientSecrets == null) { clientSecrets = new TreeSet<>(); } clientSecrets.add(new ClientSecretEntity(clientSecret, this, primary)); } @Transient public String getDecryptedClientSecret() { return decryptedClientSecret; } public void setDecryptedClientSecret(String decryptedClientSecret) { this.decryptedClientSecret = decryptedClientSecret; } /** * Whether this client is limited to a specific scope. If false, the scope * of the authentication request will be ignored. * * @return Whether this client is limited to a specific scope. */ @Override @Transient public boolean isScoped() { return this.clientScopes != null && !this.clientScopes.isEmpty(); } /** * The scope of this client. Ignored if the {@link #isScoped() client isn't * scoped}. * * @return The scope of this client. */ @Override @Transient public Set<String> getScope() { Set<String> sps = new HashSet<String>(); if (clientScopes != null && !clientScopes.isEmpty()) { for (ClientScopeEntity cse : clientScopes) { sps.add(cse.getScopeType()); } } return sps; } /** * The grant types for which this client is authorized. * * @return The grant types for which this client is authorized. */ @Override @Transient public Set<String> getAuthorizedGrantTypes() { Set<String> grants = new HashSet<String>(); if (clientAuthorizedGrantTypes != null && !clientAuthorizedGrantTypes.isEmpty()) { for (ClientAuthorisedGrantTypeEntity cagt : clientAuthorizedGrantTypes) { grants.add(cagt.getGrantType()); } } return grants; } /** * The pre-defined redirect URI for this client to use during the * "authorization_code" access grant. See OAuth spec, section 4.1.1. * * @return The pre-defined redirect URI for this client. */ @Override @Transient public Set<String> getRegisteredRedirectUri() { Set<String> redirects = null; if (clientRegisteredRedirectUris != null && !clientRegisteredRedirectUris.isEmpty()) { redirects = new HashSet<String>(); for (ClientRedirectUriEntity cru : clientRegisteredRedirectUris) { redirects.add(cru.getRedirectUri()); } } return redirects; } /** * Get the authorities that are granted to the OAuth client. Note that these * are NOT the authorities that are granted to the user with an authorized * access token. Instead, these authorities are inherent to the client * itself. * * @return The authorities. */ @Override @Transient public Collection<GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> gas = new ArrayList<GrantedAuthority>(); for (ClientGrantedAuthorityEntity cgae : clientGrantedAuthorities) { gas.add(cgae); } return gas; } /** * The access token validity period for this client. Zero or negative for * unlimited. * * @return the access token validity period */ @Override @Transient public Integer getAccessTokenValiditySeconds() { return accessTokenValiditySeconds; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ClientDetailsEntity that = (ClientDetailsEntity) o; if (!clientId.equals(that.clientId)) return false; return true; } @Override public int hashCode() { return clientId.hashCode(); } @Override @Transient public Integer getRefreshTokenValiditySeconds() { // Not currently required return null; } @Override @Transient public Map<String, Object> getAdditionalInformation() { // Not currently required return null; } @Override @Transient public boolean isAutoApprove(String scope) { return false; } @Column(name = "authentication_provider_id") public String getAuthenticationProviderId() { return authenticationProviderId; } public void setAuthenticationProviderId(String authenticationProviderId) { this.authenticationProviderId = authenticationProviderId; } @Column(name = "allow_auto_deprecate") public Boolean getAllowAutoDeprecate() { return allowAutoDeprecate; } public void setAllowAutoDeprecate(boolean allowAutoDeprecate) { this.allowAutoDeprecate = allowAutoDeprecate; } }