diff --git a/pom.xml b/pom.xml
index c8f498bed..967761740 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,10 +7,20 @@
OpenIdConnect
pom
0.1
-
- spring-security-oauth/spring-security-oauth2
- server
-
+
+
+
+
+ true
+
+ default
+
+ spring-security-oauth/spring-security-oauth2
+ server
+
+
+
+
1.6
3.1.0.RELEASE
diff --git a/server/src/main/java/org/mitre/oauth2/exception/ClientNotFoundException.java b/server/src/main/java/org/mitre/oauth2/exception/ClientNotFoundException.java
new file mode 100644
index 000000000..94fe88eff
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/exception/ClientNotFoundException.java
@@ -0,0 +1,50 @@
+package org.mitre.oauth2.exception;
+/**
+ *
+ */
+
+
+/**
+ * @author aanganes
+ *
+ */
+public class ClientNotFoundException extends RuntimeException {
+
+ /**
+ *
+ */
+ private static final Long serialVersionUID = 1L;
+
+ /**
+ *
+ */
+ public ClientNotFoundException() {
+ // TODO Auto-generated constructor stub
+ }
+
+ /**
+ * @param message
+ */
+ public ClientNotFoundException(String message) {
+ super(message);
+ // TODO Auto-generated constructor stub
+ }
+
+ /**
+ * @param cause
+ */
+ public ClientNotFoundException(Throwable cause) {
+ super(cause);
+ // TODO Auto-generated constructor stub
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public ClientNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ // TODO Auto-generated constructor stub
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java b/server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java
new file mode 100644
index 000000000..78ff82b2c
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java
@@ -0,0 +1,15 @@
+package org.mitre.oauth2.exception;
+
+public class DuplicateClientIdException extends RuntimeException {
+
+ public DuplicateClientIdException(String clientId) {
+ super("Duplicate client id: " + clientId);
+ }
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/exception/PermissionDeniedException.java b/server/src/main/java/org/mitre/oauth2/exception/PermissionDeniedException.java
new file mode 100644
index 000000000..c6f2bea6e
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/exception/PermissionDeniedException.java
@@ -0,0 +1,49 @@
+/**
+ *
+ */
+package org.mitre.oauth2.exception;
+
+/**
+ * @author AANGANES
+ *
+ */
+public class PermissionDeniedException extends RuntimeException {
+
+ /**
+ *
+ */
+ private static final Long serialVersionUID = 1L;
+
+ /**
+ *
+ */
+ public PermissionDeniedException() {
+ // TODO Auto-generated constructor stub
+ }
+
+ /**
+ * @param message
+ */
+ public PermissionDeniedException(String message) {
+ super(message);
+ // TODO Auto-generated constructor stub
+ }
+
+ /**
+ * @param cause
+ */
+ public PermissionDeniedException(Throwable cause) {
+ super(cause);
+ // TODO Auto-generated constructor stub
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public PermissionDeniedException(String message, Throwable cause) {
+ super(message, cause);
+ // TODO Auto-generated constructor stub
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java b/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java
new file mode 100644
index 000000000..247515d7b
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java
@@ -0,0 +1,473 @@
+/**
+ *
+ */
+package org.mitre.oauth2.model;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.Basic;
+import javax.persistence.CollectionTable;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.provider.ClientDetails;
+
+/**
+ * @author jricher
+ *
+ */
+@Entity
+@Table(name="clientdetails")
+@NamedQueries({
+ @NamedQuery(name = "ClientDetailsEntity.findAll", query = "SELECT c FROM ClientDetailsEntity c")
+})
+public class ClientDetailsEntity implements ClientDetails {
+
+ /**
+ * Create a blank ClientDetailsEntity
+ */
+ public ClientDetailsEntity() {
+
+ }
+
+ private String clientId;
+ private String clientSecret;
+ private Set scope;
+ private Set authorizedGrantTypes;
+ private Set authorities = Collections.emptySet();
+ private String clientName;
+ private String clientDescription;
+ private boolean allowRefresh = false; // do we allow refresh tokens for this client?
+ private Long accessTokenTimeout; // in seconds
+ private Long refreshTokenTimeout; // in seconds
+ private String owner; // userid of who registered it
+ private String registeredRedirectUri;
+ private Set resourceIds;
+
+ // TODO:
+ /*
+ private boolean allowMultipleAccessTokens; // do we allow multiple access tokens, or not?
+ private boolean reuseRefreshToken; // do we let someone reuse a refresh token?
+ */
+
+ /**
+ * @return the clientId
+ */
+ @Id
+ public String getClientId() {
+ return clientId;
+ }
+
+ /**
+ * @param clientId The OAuth2 client_id, must be unique to this client
+ */
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ /**
+ * @return the clientSecret
+ */
+ @Basic
+ public String getClientSecret() {
+ return clientSecret;
+ }
+
+ /**
+ * @param clientSecret the OAuth2 client_secret (optional)
+ */
+ public void setClientSecret(String clientSecret) {
+ this.clientSecret = clientSecret;
+ }
+
+ /**
+ * @return the scope
+ */
+ @ElementCollection(fetch = FetchType.EAGER)
+ @CollectionTable(
+ name="scope",
+ joinColumns=@JoinColumn(name="owner_id")
+ )
+ public Set getScope() {
+ return scope;
+ }
+
+ /**
+ * @param scope the set of scopes allowed to be issued to this client
+ */
+ public void setScope(Set scope) {
+ this.scope = scope;
+ }
+
+ /**
+ * @return the authorizedGrantTypes
+ */
+ @ElementCollection(fetch = FetchType.EAGER)
+ @CollectionTable(
+ name="authorizedgranttypes",
+ joinColumns=@JoinColumn(name="owner_id")
+ )
+ public Set getAuthorizedGrantTypes() {
+ return authorizedGrantTypes;
+ }
+
+ /**
+ * @param authorizedGrantTypes the OAuth2 grant types that this client is allowed to use
+ */
+ public void setAuthorizedGrantTypes(Set authorizedGrantTypes) {
+ this.authorizedGrantTypes = authorizedGrantTypes;
+ }
+
+ /**
+ * @return the authorities
+ */
+ @ElementCollection(fetch = FetchType.EAGER)
+ @CollectionTable(
+ name="authorities",
+ joinColumns=@JoinColumn(name="owner_id")
+ )
+ public Set getAuthorities() {
+ return authorities;
+ }
+
+ /**
+ * @param authorities the Spring Security authorities this client is given
+ */
+ public void setAuthorities(Set authorities) {
+ this.authorities = authorities;
+ }
+
+ /**
+ * If the clientSecret is not null, then it is always required.
+ */
+ @Override
+ public boolean isSecretRequired() {
+ return getClientSecret() != null;
+ }
+
+ /**
+ * If the scope list is not null or empty, then this client has been scoped.
+ */
+ @Override
+ public boolean isScoped() {
+ return getScope() != null && !getScope().isEmpty();
+ }
+
+ /**
+ * @return the clientName
+ */
+ @Basic
+ public String getClientName() {
+ return clientName;
+ }
+
+ /**
+ * @param clientName Human-readable name of the client (optional)
+ */
+ public void setClientName(String clientName) {
+ this.clientName = clientName;
+ }
+
+ /**
+ * @return the clientDescription
+ */
+ @Basic
+ public String getClientDescription() {
+ return clientDescription;
+ }
+
+ /**
+ * @param clientDescription Human-readable long description of the client (optional)
+ */
+ public void setClientDescription(String clientDescription) {
+ this.clientDescription = clientDescription;
+ }
+
+ /**
+ * @return the allowRefresh
+ */
+ @Basic
+ public boolean isAllowRefresh() {
+ return allowRefresh;
+ }
+
+ /**
+ * @param allowRefresh Whether to allow for issuance of refresh tokens or not (defaults to false)
+ */
+ public void setAllowRefresh(boolean allowRefresh) {
+ this.allowRefresh = allowRefresh;
+ }
+
+ /**
+ * @param accessTokenTimeout Lifetime of access tokens, in seconds (optional - leave null for no timeout)
+ */
+ @Basic
+ public Long getAccessTokenTimeout() {
+ return accessTokenTimeout;
+ }
+
+ /**
+ * @param accessTokenTimeout the accessTokenTimeout to set
+ */
+ public void setAccessTokenTimeout(Long accessTokenTimeout) {
+ this.accessTokenTimeout = accessTokenTimeout;
+ }
+
+ /**
+ * @return the refreshTokenTimeout
+ */
+ @Basic
+ public Long getRefreshTokenTimeout() {
+ return refreshTokenTimeout;
+ }
+
+ /**
+ * @param refreshTokenTimeout Lifetime of refresh tokens, in seconds (optional - leave null for no timeout)
+ */
+ public void setRefreshTokenTimeout(Long refreshTokenTimeout) {
+ this.refreshTokenTimeout = refreshTokenTimeout;
+ }
+
+ /**
+ * @return the owner
+ */
+ @Basic
+ public String getOwner() {
+ return owner;
+ }
+
+ /**
+ * @param owner User ID of the person who registered this client (optional)
+ */
+ public void setOwner(String owner) {
+ this.owner = owner;
+ }
+
+ /**
+ * @return the registeredRedirectUri
+ */
+ @Basic
+ public String getRegisteredRedirectUri() {
+ return registeredRedirectUri;
+ }
+
+ /**
+ * @param registeredRedirectUri the registeredRedirectUri to set
+ */
+ public void setRegisteredRedirectUri(String registeredRedirectUri) {
+ this.registeredRedirectUri = registeredRedirectUri;
+ }
+
+ /**
+ * @return the resourceIds
+ */
+ public Set getResourceIds() {
+ return resourceIds;
+ }
+
+ /**
+ * @param resourceIds the resourceIds to set
+ */
+ @ElementCollection(fetch = FetchType.EAGER)
+ @CollectionTable(
+ name="resource_ids",
+ joinColumns=@JoinColumn(name="owner_id")
+ )
+ public void setResourceIds(Set resourceIds) {
+ this.resourceIds = resourceIds;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "ClientDetailsEntity [" + (clientId != null ? "clientId=" + clientId + ", " : "") + (scope != null ? "scope=" + scope + ", " : "") + (clientName != null ? "clientName=" + clientName + ", " : "") + (owner != null ? "owner=" + owner : "") + "]";
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((clientId == null) ? 0 : clientId.hashCode());
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ClientDetailsEntity other = (ClientDetailsEntity) obj;
+ if (clientId == null) {
+ if (other.clientId != null) {
+ return false;
+ }
+ } else if (!clientId.equals(other.clientId)) {
+ return false;
+ }
+ return true;
+ }
+
+ public static ClientDetailsEntityBuilder makeBuilder() {
+ return new ClientDetailsEntityBuilder();
+ }
+
+ public static class ClientDetailsEntityBuilder {
+ private ClientDetailsEntity instance;
+
+ private ClientDetailsEntityBuilder() {
+ instance = new ClientDetailsEntity();
+ }
+
+ /**
+ * @param clientId
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setClientId(java.lang.String)
+ */
+ public ClientDetailsEntityBuilder setClientId(String clientId) {
+ instance.setClientId(clientId);
+ return this;
+ }
+
+ /**
+ * @param clientSecret
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setClientSecret(java.lang.String)
+ */
+ public ClientDetailsEntityBuilder setClientSecret(String clientSecret) {
+ instance.setClientSecret(clientSecret);
+ return this;
+ }
+
+ /**
+ * @param scope
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setScope(java.util.List)
+ */
+ public ClientDetailsEntityBuilder setScope(Set scope) {
+ instance.setScope(scope);
+ return this;
+ }
+
+ /**
+ * @param authorizedGrantTypes
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setAuthorizedGrantTypes(java.util.List)
+ */
+ public ClientDetailsEntityBuilder setAuthorizedGrantTypes(Set authorizedGrantTypes) {
+ instance.setAuthorizedGrantTypes(authorizedGrantTypes);
+ return this;
+ }
+
+ /**
+ * @param authorities
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setAuthorities(java.util.List)
+ */
+ public ClientDetailsEntityBuilder setAuthorities(Set authorities) {
+ instance.setAuthorities(authorities);
+ return this;
+ }
+
+ /**
+ * @param clientName
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setClientName(java.lang.String)
+ */
+ public ClientDetailsEntityBuilder setClientName(String clientName) {
+ instance.setClientName(clientName);
+ return this;
+ }
+
+ /**
+ * @param clientDescription
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setClientDescription(java.lang.String)
+ */
+ public ClientDetailsEntityBuilder setClientDescription(String clientDescription) {
+ instance.setClientDescription(clientDescription);
+ return this;
+ }
+
+ /**
+ * @param allowRefresh
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setAllowRefresh(boolean)
+ */
+ public ClientDetailsEntityBuilder setAllowRefresh(boolean allowRefresh) {
+ instance.setAllowRefresh(allowRefresh);
+ return this;
+ }
+
+ /**
+ * @param accessTokenTimeout
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setAccessTokenTimeout(java.lang.Long)
+ */
+ public ClientDetailsEntityBuilder setAccessTokenTimeout(Long accessTokenTimeout) {
+ instance.setAccessTokenTimeout(accessTokenTimeout);
+ return this;
+ }
+
+ /**
+ * @param refreshTokenTimeout
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setRefreshTokenTimeout(java.lang.Long)
+ */
+ public ClientDetailsEntityBuilder setRefreshTokenTimeout(Long refreshTokenTimeout) {
+ instance.setRefreshTokenTimeout(refreshTokenTimeout);
+ return this;
+ }
+
+ /**
+ * @param owner
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setOwner(java.lang.String)
+ */
+ public ClientDetailsEntityBuilder setOwner(String owner) {
+ instance.setOwner(owner);
+ return this;
+ }
+
+
+
+ /**
+ * Complete the builder
+ * @return
+ */
+ public ClientDetailsEntity finish() {
+ return instance;
+ }
+
+ /**
+ * @param registeredRedirectUri
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setRegisteredRedirectUri(java.lang.String)
+ */
+ public ClientDetailsEntityBuilder setRegisteredRedirectUri(String registeredRedirectUri) {
+ instance.setRegisteredRedirectUri(registeredRedirectUri);
+ return this;
+ }
+
+ /**
+ * @param resourceIds
+ * @see org.mitre.oauth2.model.ClientDetailsEntity#setResourceIds(java.util.List)
+ */
+ public ClientDetailsEntityBuilder setResourceIds(Set resourceIds) {
+ instance.setResourceIds(resourceIds);
+ return this;
+ }
+
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntityFactory.java b/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntityFactory.java
new file mode 100644
index 000000000..5f9f68ccb
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntityFactory.java
@@ -0,0 +1,9 @@
+package org.mitre.oauth2.model;
+
+import org.mitre.oauth2.model.ClientDetailsEntity.ClientDetailsEntityBuilder;
+
+public interface ClientDetailsEntityFactory {
+
+ public ClientDetailsEntity createClient(String clientId, String clientSecret);
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/ClientGeneratorFactory.java b/server/src/main/java/org/mitre/oauth2/model/ClientGeneratorFactory.java
new file mode 100644
index 000000000..ceaa22cf6
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/ClientGeneratorFactory.java
@@ -0,0 +1,32 @@
+package org.mitre.oauth2.model;
+
+import java.util.UUID;
+
+import org.apache.commons.codec.binary.Base64;
+import org.mitre.oauth2.model.ClientDetailsEntity.ClientDetailsEntityBuilder;
+import org.springframework.stereotype.Service;
+
+/**
+ * A factory for making OAuth2 clients with autogenerated IDs and secrets (as desired)
+ * @author jricher
+ *
+ */
+@Service
+public class ClientGeneratorFactory implements ClientDetailsEntityFactory {
+
+ @Override
+ public ClientDetailsEntity createClient(String clientId, String clientSecret) {
+ ClientDetailsEntityBuilder builder = ClientDetailsEntity.makeBuilder();
+ if (clientId == null) {
+ clientId = UUID.randomUUID().toString();
+ }
+ builder.setClientId(clientId);
+ if (clientSecret == null) {
+ clientSecret = Base64.encodeBase64((UUID.randomUUID().toString() + UUID.randomUUID().toString()).getBytes()).toString();
+ }
+ builder.setClientSecret(clientSecret);
+
+ return builder.finish();
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java b/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java
new file mode 100644
index 000000000..11aebde73
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java
@@ -0,0 +1,210 @@
+/**
+ *
+ */
+package org.mitre.oauth2.model;
+
+import java.util.Date;
+import java.util.Set;
+
+import javax.persistence.Basic;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Lob;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.Transient;
+
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.security.oauth2.common.OAuth2RefreshToken;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+
+/**
+ * @author jricher
+ *
+ */
+@Entity
+@Table(name="accesstoken")
+@NamedQueries({
+ @NamedQuery(name = "OAuth2AccessTokenEntity.getByRefreshToken", query = "select a from OAuth2AccessTokenEntity a where a.refreshToken = :refreshToken"),
+ @NamedQuery(name = "OAuth2AccessTokenEntity.getByClient", query = "select a from OAuth2AccessTokenEntity a where a.client = :client"),
+ @NamedQuery(name = "OAuth2AccessTokenEntity.getExpired", query = "select a from OAuth2AccessTokenEntity a where a.expiration is not null and a.expiration < current_timestamp")
+})
+public class OAuth2AccessTokenEntity extends OAuth2AccessToken {
+
+ private ClientDetailsEntity client;
+
+ private OAuth2Authentication authentication; // the authentication that made this access
+
+ /**
+ *
+ */
+ public OAuth2AccessTokenEntity() {
+ super(null);
+ }
+
+
+ /**
+ * @return the authentication
+ */
+ @Lob
+ @Basic
+ public OAuth2Authentication getAuthentication() {
+ return authentication;
+ }
+
+
+ /**
+ * @param authentication the authentication to set
+ */
+ public void setAuthentication(OAuth2Authentication authentication) {
+ this.authentication = authentication;
+ }
+
+
+ /**
+ * @return the client
+ */
+ @ManyToOne
+ @JoinColumn(name = "client_id")
+ public ClientDetailsEntity getClient() {
+ return client;
+ }
+
+
+ /**
+ * @param client the client to set
+ */
+ public void setClient(ClientDetailsEntity client) {
+ this.client = client;
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2AccessToken#getValue()
+ */
+ @Override
+ @Id
+ @Column(name="id")
+ public String getValue() {
+ // TODO Auto-generated method stub
+ return super.getValue();
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setValue(java.lang.String)
+ */
+ @Override
+ public void setValue(String value) {
+ // TODO Auto-generated method stub
+ super.setValue(value);
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2AccessToken#getExpiration()
+ */
+ @Override
+ @Basic
+ @Temporal(javax.persistence.TemporalType.TIMESTAMP)
+ public Date getExpiration() {
+ // TODO Auto-generated method stub
+ return super.getExpiration();
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setExpiration(java.util.Date)
+ */
+ @Override
+ public void setExpiration(Date expiration) {
+ // TODO Auto-generated method stub
+ super.setExpiration(expiration);
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2AccessToken#getTokenType()
+ */
+ @Override
+ @Basic
+ public String getTokenType() {
+ // TODO Auto-generated method stub
+ return super.getTokenType();
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setTokenType(java.lang.String)
+ */
+ @Override
+ public void setTokenType(String tokenType) {
+ // TODO Auto-generated method stub
+ super.setTokenType(tokenType);
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2AccessToken#getRefreshToken()
+ */
+ @Override
+ @ManyToOne
+ @JoinColumn(name="refresh_token_id")
+ public OAuth2RefreshTokenEntity getRefreshToken() {
+ // TODO Auto-generated method stub
+ return (OAuth2RefreshTokenEntity) super.getRefreshToken();
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setRefreshToken(org.springframework.security.oauth2.common.OAuth2RefreshToken)
+ */
+ public void setRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
+ // TODO Auto-generated method stub
+ super.setRefreshToken(refreshToken);
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setRefreshToken(org.springframework.security.oauth2.common.OAuth2RefreshToken)
+ */
+ @Override
+ public void setRefreshToken(OAuth2RefreshToken refreshToken) {
+ if (!(refreshToken instanceof OAuth2RefreshTokenEntity)) {
+ // TODO: make a copy constructor instead....
+ throw new IllegalArgumentException("Not a storable refresh token entity!");
+ }
+ // force a pass through to the entity version
+ setRefreshToken((OAuth2RefreshTokenEntity)refreshToken);
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2AccessToken#getScope()
+ */
+ @Override
+ @ElementCollection(fetch=FetchType.EAGER)
+ @CollectionTable(
+ joinColumns=@JoinColumn(name="owner_id"),
+ name="scope"
+ )
+ public Set getScope() {
+ // TODO Auto-generated method stub
+ return super.getScope();
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setScope(java.util.Set)
+ */
+ @Override
+ public void setScope(Set scope) {
+ // TODO Auto-generated method stub
+ super.setScope(scope);
+ }
+
+ @Transient
+ public boolean isExpired() {
+ return getExpiration() == null ? false : System.currentTimeMillis() > getExpiration().getTime();
+ }
+
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntityFactory.java b/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntityFactory.java
new file mode 100644
index 000000000..b4bd1ba38
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntityFactory.java
@@ -0,0 +1,7 @@
+package org.mitre.oauth2.model;
+
+public interface OAuth2AccessTokenEntityFactory {
+
+ public OAuth2AccessTokenEntity createNewAccessToken();
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java b/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java
new file mode 100644
index 000000000..72d73f48f
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java
@@ -0,0 +1,136 @@
+/**
+ *
+ */
+package org.mitre.oauth2.model;
+
+import java.util.Date;
+import java.util.Set;
+
+import javax.persistence.Basic;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.Transient;
+
+import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
+
+/**
+ * @author jricher
+ *
+ */
+@Entity
+@Table(name="refreshtoken")
+@NamedQueries({
+ @NamedQuery(name = "OAuth2RefreshTokenEntity.getByClient", query = "select r from OAuth2RefreshTokenEntity r where r.client = :client"),
+ @NamedQuery(name = "OAuth2RefreshTokenEntity.getExpired", query = "select r from OAuth2RefreshTokenEntity r where r.expiration is not null and r.expiration < current_timestamp")
+})
+public class OAuth2RefreshTokenEntity extends ExpiringOAuth2RefreshToken {
+
+ private ClientDetailsEntity client;
+
+ private Set scope; // we save the scope issued to the refresh token so that we can reissue a new access token
+
+ /**
+ *
+ */
+ public OAuth2RefreshTokenEntity() {
+ // TODO Auto-generated constructor stub
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2RefreshToken#getValue()
+ */
+ @Override
+ @Id
+ @Column(name="id")
+ public String getValue() {
+ // TODO Auto-generated method stub
+ return super.getValue();
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.OAuth2RefreshToken#setValue(java.lang.String)
+ */
+ @Override
+ public void setValue(String value) {
+ // TODO Auto-generated method stub
+ super.setValue(value);
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken#getExpiration()
+ */
+ @Override
+ @Basic
+ @Temporal(javax.persistence.TemporalType.TIMESTAMP)
+ public Date getExpiration() {
+ // TODO Auto-generated method stub
+ return super.getExpiration();
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken#setExpiration(java.util.Date)
+ */
+ @Override
+ public void setExpiration(Date expiration) {
+ // TODO Auto-generated method stub
+ super.setExpiration(expiration);
+ }
+
+ /**
+ * Has this token expired?
+ * @return true if it has a timeout set and the timeout has passed
+ */
+ @Transient
+ public boolean isExpired() {
+ return getExpiration() == null ? false : System.currentTimeMillis() > getExpiration().getTime();
+ }
+
+ /**
+ * @return the client
+ */
+ @ManyToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "client_id")
+ public ClientDetailsEntity getClient() {
+ return client;
+ }
+
+
+ /**
+ * @param client the client to set
+ */
+ public void setClient(ClientDetailsEntity client) {
+ this.client = client;
+ }
+
+ /**
+ * @return the scope
+ */
+ @ElementCollection(fetch = FetchType.EAGER)
+ @CollectionTable(
+ joinColumns=@JoinColumn(name="owner_id"),
+ name="scope"
+ )
+ public Set getScope() {
+ return scope;
+ }
+
+ /**
+ * @param scope the scope to set
+ */
+ public void setScope(Set scope) {
+ this.scope = scope;
+ }
+
+
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntityFactory.java b/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntityFactory.java
new file mode 100644
index 000000000..2a6ac4785
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntityFactory.java
@@ -0,0 +1,7 @@
+package org.mitre.oauth2.model;
+
+public interface OAuth2RefreshTokenEntityFactory {
+
+ public OAuth2RefreshTokenEntity createNewRefreshToken();
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/UUIDTokenFactory.java b/server/src/main/java/org/mitre/oauth2/model/UUIDTokenFactory.java
new file mode 100644
index 000000000..91f93856a
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/UUIDTokenFactory.java
@@ -0,0 +1,39 @@
+package org.mitre.oauth2.model;
+
+import java.util.UUID;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class UUIDTokenFactory implements OAuth2AccessTokenEntityFactory, OAuth2RefreshTokenEntityFactory {
+
+ /**
+ * Create a new access token and set its value to a random UUID
+ */
+ @Override
+ public OAuth2AccessTokenEntity createNewAccessToken() {
+ // create our token container
+ OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();
+
+ // set a random value (TODO: support JWT)
+ String tokenValue = UUID.randomUUID().toString();
+ token.setValue(tokenValue);
+
+ return token;
+ }
+
+ /**
+ * Create a new refresh token and set its value to a random UUID
+ */
+ @Override
+ public OAuth2RefreshTokenEntity createNewRefreshToken() {
+ OAuth2RefreshTokenEntity refreshToken = new OAuth2RefreshTokenEntity();
+
+ // set a random value for the refresh
+ String refreshTokenValue = UUID.randomUUID().toString();
+ refreshToken.setValue(refreshTokenValue);
+
+ return refreshToken;
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/serializer/JSONOAuthClientView.java b/server/src/main/java/org/mitre/oauth2/model/serializer/JSONOAuthClientView.java
new file mode 100644
index 000000000..843d7e3ac
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/serializer/JSONOAuthClientView.java
@@ -0,0 +1,66 @@
+package org.mitre.oauth2.model.serializer;
+
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.validation.BeanPropertyBindingResult;
+import org.springframework.web.servlet.view.AbstractView;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+public class JSONOAuthClientView extends AbstractView {
+
+ @Override
+ protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
+ Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
+
+ @Override
+ public boolean shouldSkipField(FieldAttributes f) {
+ return false;
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class> clazz) {
+ // skip the JPA binding wrapper
+ if (clazz.equals(BeanPropertyBindingResult.class)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ })
+ .registerTypeAdapter(GrantedAuthority.class, new JsonSerializer() {
+ @Override
+ public JsonElement serialize(GrantedAuthority src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(src.getAuthority());
+ }
+ })
+ .create();
+
+ response.setContentType("application/json");
+
+ Writer out = response.getWriter();
+
+ Object obj = model.get("entity");
+ if (obj == null) {
+ obj = model;
+ }
+
+ gson.toJson(obj, out);
+
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/serializer/TokenIntrospection.java b/server/src/main/java/org/mitre/oauth2/model/serializer/TokenIntrospection.java
new file mode 100644
index 000000000..28af3d3f2
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/serializer/TokenIntrospection.java
@@ -0,0 +1,100 @@
+package org.mitre.oauth2.model.serializer;
+
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.text.DateFormat;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.validation.BeanPropertyBindingResult;
+import org.springframework.web.servlet.view.AbstractView;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+public class TokenIntrospection extends AbstractView {
+
+ @Override
+ protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
+
+ Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
+
+ @Override
+ public boolean shouldSkipField(FieldAttributes f) {
+ /*
+ if (f.getDeclaringClass().isAssignableFrom(OAuth2AccessTokenEntity.class)) {
+ // we don't want to serialize the whole object, just the scope and timeout
+ if (f.getName().equals("scope")) {
+ return false;
+ } else if (f.getName().equals("expiration")) {
+ return false;
+ } else {
+ // skip everything else on this class
+ return true;
+ }
+ } else {
+ // serialize other classes without filter (lists and sets and things)
+ return false;
+ }
+ */
+ return false;
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class> clazz) {
+ // skip the JPA binding wrapper
+ if (clazz.equals(BeanPropertyBindingResult.class)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ })
+ .registerTypeAdapter(OAuth2AccessTokenEntity.class, new JsonSerializer() {
+ public JsonElement serialize(OAuth2AccessTokenEntity src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject token = new JsonObject();
+
+ token.addProperty("valid", true);
+
+ JsonArray scopes = new JsonArray();
+ for (String scope : src.getScope()) {
+ scopes.add(new JsonPrimitive(scope));
+ }
+ token.add("scope", scopes);
+
+ token.add("expires", context.serialize(src.getExpiration()));
+
+ return token;
+ }
+
+ })
+ .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
+ .create();
+
+ response.setContentType("application/json");
+
+ Writer out = response.getWriter();
+
+ Object obj = model.get("entity");
+ if (obj == null) {
+ obj = model;
+ }
+
+ gson.toJson(obj, out);
+
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java b/server/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java
new file mode 100644
index 000000000..cd9de162f
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java
@@ -0,0 +1,22 @@
+package org.mitre.oauth2.repository;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.springframework.security.oauth2.provider.ClientDetails;
+import org.springframework.stereotype.Repository;
+
+public interface OAuth2ClientRepository {
+
+ public ClientDetailsEntity getClientById(String clientId);
+
+ public ClientDetailsEntity saveClient(ClientDetailsEntity client);
+
+ public void deleteClient(ClientDetailsEntity client);
+
+ public ClientDetailsEntity updateClient(String clientId, ClientDetailsEntity client);
+
+ public Collection getAllClients();
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java b/server/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java
new file mode 100644
index 000000000..475a5c0c3
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java
@@ -0,0 +1,35 @@
+package org.mitre.oauth2.repository;
+
+import java.util.List;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+
+public interface OAuth2TokenRepository {
+
+ public OAuth2AccessTokenEntity saveAccessToken(OAuth2AccessTokenEntity token);
+
+ public OAuth2RefreshTokenEntity getRefreshTokenByValue(String refreshTokenValue);
+
+ public void clearAccessTokensForRefreshToken(OAuth2RefreshTokenEntity refreshToken);
+
+ public void removeRefreshToken(OAuth2RefreshTokenEntity refreshToken);
+
+ public OAuth2RefreshTokenEntity saveRefreshToken(OAuth2RefreshTokenEntity refreshToken);
+
+ public OAuth2AccessTokenEntity getAccessTokenByValue(String accessTokenValue);
+
+ public void removeAccessToken(OAuth2AccessTokenEntity accessToken);
+
+ public void clearTokensForClient(ClientDetailsEntity client);
+
+ public List getAccessTokensForClient(ClientDetailsEntity client);
+
+ public List getRefreshTokensForClient(ClientDetailsEntity client);
+
+ public List getExpiredAccessTokens();
+
+ public List getExpiredRefreshTokens();
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java b/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java
new file mode 100644
index 000000000..7022270bb
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java
@@ -0,0 +1,74 @@
+package org.mitre.oauth2.repository.impl;
+
+import java.util.Collection;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.TypedQuery;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.repository.OAuth2ClientRepository;
+import org.mitre.util.jpa.JpaUtil;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author jricher
+ *
+ */
+@Repository
+@Transactional
+public class JpaOAuth2ClientRepository implements OAuth2ClientRepository {
+
+ @PersistenceContext
+ private EntityManager manager;
+
+ public JpaOAuth2ClientRepository() {
+
+ }
+
+ public JpaOAuth2ClientRepository(EntityManager manager) {
+ this.manager = manager;
+ }
+
+ /* (non-Javadoc)
+ * @see org.mitre.oauth2.repository.OAuth2ClientRepository#getClientById(java.lang.String)
+ */
+ @Override
+ public ClientDetailsEntity getClientById(String clientId) {
+ return manager.find(ClientDetailsEntity.class, clientId);
+ }
+
+ /* (non-Javadoc)
+ * @see org.mitre.oauth2.repository.OAuth2ClientRepository#saveClient(org.mitre.oauth2.model.ClientDetailsEntity)
+ */
+ @Override
+ public ClientDetailsEntity saveClient(ClientDetailsEntity client) {
+ return JpaUtil.saveOrUpdate(client.getClientId(), manager, client);
+ }
+
+ /* (non-Javadoc)
+ * @see org.mitre.oauth2.repository.OAuth2ClientRepository#deleteClient(org.mitre.oauth2.model.ClientDetailsEntity)
+ */
+ @Override
+ public void deleteClient(ClientDetailsEntity client) {
+ ClientDetailsEntity found = getClientById(client.getClientId());
+ if (found != null) {
+ manager.remove(found);
+ } else {
+ throw new IllegalArgumentException("Client not found: " + client);
+ }
+ }
+
+ @Override
+ public ClientDetailsEntity updateClient(String clientId, ClientDetailsEntity client) {
+ return JpaUtil.saveOrUpdate(clientId, manager, client);
+ }
+
+ @Override
+ public Collection getAllClients() {
+ TypedQuery query = manager.createNamedQuery("ClientDetailsEntity.findAll", ClientDetailsEntity.class);
+ return query.getResultList();
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java b/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java
new file mode 100644
index 000000000..85cd9041d
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java
@@ -0,0 +1,138 @@
+package org.mitre.oauth2.repository.impl;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.TypedQuery;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+import org.mitre.oauth2.repository.OAuth2TokenRepository;
+import org.mitre.util.jpa.JpaUtil;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+@Transactional
+public class JpaOAuth2TokenRepository implements OAuth2TokenRepository {
+
+ @PersistenceContext
+ private EntityManager manager;
+
+ @Override
+ public OAuth2AccessTokenEntity getAccessTokenByValue(String accessTokenValue) {
+ return manager.find(OAuth2AccessTokenEntity.class, accessTokenValue);
+ }
+
+ @Override
+ @Transactional
+ public OAuth2AccessTokenEntity saveAccessToken(OAuth2AccessTokenEntity token) {
+ return JpaUtil.saveOrUpdate(token.getValue(), manager, token);
+ }
+
+ @Override
+ @Transactional
+ public void removeAccessToken(OAuth2AccessTokenEntity accessToken) {
+ OAuth2AccessTokenEntity found = getAccessTokenByValue(accessToken.getValue());
+ if (found != null) {
+ manager.remove(found);
+ } else {
+ throw new IllegalArgumentException("Access token not found: " + accessToken);
+ }
+ }
+
+ @Override
+ @Transactional
+ public void clearAccessTokensForRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
+ TypedQuery query = manager.createNamedQuery("OAuth2AccessTokenEntity.getByRefreshToken", OAuth2AccessTokenEntity.class);
+ query.setParameter("refreshToken", refreshToken);
+ List accessTokens = query.getResultList();
+ for (OAuth2AccessTokenEntity accessToken : accessTokens) {
+ removeAccessToken(accessToken);
+ }
+ }
+
+ @Override
+ public OAuth2RefreshTokenEntity getRefreshTokenByValue(String refreshTokenValue) {
+ return manager.find(OAuth2RefreshTokenEntity.class, refreshTokenValue);
+ }
+
+ @Override
+ @Transactional
+ public OAuth2RefreshTokenEntity saveRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
+ return JpaUtil.saveOrUpdate(refreshToken.getValue(), manager, refreshToken);
+ }
+
+ @Override
+ @Transactional
+ public void removeRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
+ OAuth2RefreshTokenEntity found = getRefreshTokenByValue(refreshToken.getValue());
+ if (found != null) {
+ manager.remove(found);
+ } else {
+ throw new IllegalArgumentException("Refresh token not found: " + refreshToken);
+ }
+ }
+
+ @Override
+ @Transactional
+ public void clearTokensForClient(ClientDetailsEntity client) {
+ TypedQuery queryA = manager.createNamedQuery("OAuth2AccessTokenEntity.getByClient", OAuth2AccessTokenEntity.class);
+ queryA.setParameter("client", client);
+ List accessTokens = queryA.getResultList();
+ for (OAuth2AccessTokenEntity accessToken : accessTokens) {
+ removeAccessToken(accessToken);
+ }
+ TypedQuery queryR = manager.createNamedQuery("OAuth2RefreshTokenEntity.getByClient", OAuth2RefreshTokenEntity.class);
+ queryR.setParameter("client", client);
+ List refreshTokens = queryR.getResultList();
+ for (OAuth2RefreshTokenEntity refreshToken : refreshTokens) {
+ removeRefreshToken(refreshToken);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getAccessTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity)
+ */
+ @Override
+ public List getAccessTokensForClient(ClientDetailsEntity client) {
+ TypedQuery queryA = manager.createNamedQuery("OAuth2AccessTokenEntity.getByClient", OAuth2AccessTokenEntity.class);
+ queryA.setParameter("client", client);
+ List accessTokens = queryA.getResultList();
+ return accessTokens;
+ }
+
+ /* (non-Javadoc)
+ * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getRefreshTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity)
+ */
+ @Override
+ public List getRefreshTokensForClient(ClientDetailsEntity client) {
+ TypedQuery queryR = manager.createNamedQuery("OAuth2RefreshTokenEntity.getByClient", OAuth2RefreshTokenEntity.class);
+ queryR.setParameter("client", client);
+ List refreshTokens = queryR.getResultList();
+ return refreshTokens;
+ }
+
+ /* (non-Javadoc)
+ * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getExpiredAccessTokens()
+ */
+ @Override
+ public List getExpiredAccessTokens() {
+ TypedQuery queryA = manager.createNamedQuery("OAuth2AccessTokenEntity.getExpired", OAuth2AccessTokenEntity.class);
+ List accessTokens = queryA.getResultList();
+ return accessTokens;
+ }
+
+ /* (non-Javadoc)
+ * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getExpiredRefreshTokens()
+ */
+ @Override
+ public List getExpiredRefreshTokens() {
+ TypedQuery queryR = manager.createNamedQuery("OAuth2RefreshTokenEntity.getExpired", OAuth2RefreshTokenEntity.class);
+ List refreshTokens = queryR.getResultList();
+ return refreshTokens;
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java b/server/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java
new file mode 100644
index 000000000..d1c9757ad
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java
@@ -0,0 +1,22 @@
+package org.mitre.oauth2.service;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+import org.springframework.security.oauth2.provider.ClientDetailsService;
+
+public interface ClientDetailsEntityService extends ClientDetailsService {
+
+ public ClientDetailsEntity loadClientByClientId(String clientId) throws OAuth2Exception;
+
+ public ClientDetailsEntity createClient(String clientId, String clientSecret, Set scope, Set grantTypes, String redirectUri, Set authorities, Set resourceIds, String name, String description, boolean allowRefresh, Long accessTokenTimeout, Long refreshTokenTimeout, String owner);
+
+ public void deleteClient(ClientDetailsEntity client);
+
+ public ClientDetailsEntity updateClient(ClientDetailsEntity oldClient, ClientDetailsEntity newClient);
+
+ public Collection getAllClients();
+}
diff --git a/server/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java b/server/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java
new file mode 100644
index 000000000..724a4cd8d
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java
@@ -0,0 +1,26 @@
+package org.mitre.oauth2.service;
+
+import java.util.List;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
+import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
+
+public interface OAuth2TokenEntityService extends AuthorizationServerTokenServices, ResourceServerTokenServices {
+
+ public OAuth2AccessTokenEntity getAccessToken(String accessTokenValue);
+
+ public OAuth2RefreshTokenEntity getRefreshToken(String refreshTokenValue);
+
+ public void revokeRefreshToken(OAuth2RefreshTokenEntity refreshToken);
+
+ public void revokeAccessToken(OAuth2AccessTokenEntity accessToken);
+
+ public List getAccessTokensForClient(ClientDetailsEntity client);
+
+ public List getRefreshTokensForClient(ClientDetailsEntity client);
+
+ public void clearExpiredTokens();
+}
diff --git a/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java b/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java
new file mode 100644
index 000000000..d865ac4fa
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java
@@ -0,0 +1,130 @@
+package org.mitre.oauth2.service.impl;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.ClientDetailsEntityFactory;
+import org.mitre.oauth2.repository.OAuth2ClientRepository;
+import org.mitre.oauth2.repository.OAuth2TokenRepository;
+import org.mitre.oauth2.service.ClientDetailsEntityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
+import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+import org.springframework.stereotype.Service;
+
+import com.google.common.base.Strings;
+
+@Service
+public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEntityService {
+
+ @Autowired
+ private OAuth2ClientRepository clientRepository;
+
+ @Autowired
+ private OAuth2TokenRepository tokenRepository;
+
+ @Autowired
+ private ClientDetailsEntityFactory clientFactory;
+
+ public DefaultOAuth2ClientDetailsEntityService() {
+
+ }
+
+ public DefaultOAuth2ClientDetailsEntityService(OAuth2ClientRepository clientRepository,
+ OAuth2TokenRepository tokenRepository, ClientDetailsEntityFactory clientFactory) {
+ this.clientRepository = clientRepository;
+ this.tokenRepository = tokenRepository;
+ this.clientFactory = clientFactory;
+ }
+
+ /**
+ * Get the client for the given ID
+ */
+ @Override
+ public ClientDetailsEntity loadClientByClientId(String clientId) throws OAuth2Exception, InvalidClientException, IllegalArgumentException {
+ if (!Strings.isNullOrEmpty(clientId)) {
+ ClientDetailsEntity client = clientRepository.getClientById(clientId);
+ if (client == null) {
+ throw new InvalidClientException("Client with id " + clientId + " was not found");
+ }
+ else {
+ return client;
+ }
+ }
+
+ throw new IllegalArgumentException("Client id must not be empty!");
+ }
+
+ /**
+ * Create a new client with the appropriate fields filled in
+ */
+ @Override
+ public ClientDetailsEntity createClient(String clientId, String clientSecret,
+ Set scope, Set grantTypes, String redirectUri, Set authorities,
+ Set resourceIds,
+ String name, String description, boolean allowRefresh, Long accessTokenTimeout,
+ Long refreshTokenTimeout, String owner) {
+
+ // TODO: check "owner" locally?
+
+ ClientDetailsEntity client = clientFactory.createClient(clientId, clientSecret);
+ client.setScope(scope);
+ client.setAuthorizedGrantTypes(grantTypes);
+ client.setRegisteredRedirectUri(redirectUri);
+ client.setAuthorities(authorities);
+ client.setClientName(name);
+ client.setClientDescription(description);
+ client.setAllowRefresh(allowRefresh);
+ client.setAccessTokenTimeout(accessTokenTimeout);
+ client.setRefreshTokenTimeout(refreshTokenTimeout);
+ client.setResourceIds(resourceIds);
+ client.setOwner(owner);
+
+ clientRepository.saveClient(client);
+
+ return client;
+
+ }
+
+ /**
+ * Delete a client and all its associated tokens
+ */
+ @Override
+ public void deleteClient(ClientDetailsEntity client) throws InvalidClientException {
+
+ if (clientRepository.getClientById(client.getClientId()) == null) {
+ throw new InvalidClientException("Client with id " + client.getClientId() + " was not found");
+ }
+
+ // clean out any tokens that this client had issued
+ tokenRepository.clearTokensForClient(client);
+
+ // take care of the client itself
+ clientRepository.deleteClient(client);
+
+ }
+
+ /**
+ * Update the oldClient with information from the newClient. The
+ * id from oldClient is retained.
+ */
+ @Override
+ public ClientDetailsEntity updateClient(ClientDetailsEntity oldClient, ClientDetailsEntity newClient) throws IllegalArgumentException {
+ if (oldClient != null && newClient != null) {
+ return clientRepository.updateClient(oldClient.getClientId(), newClient);
+ }
+ throw new IllegalArgumentException("Neither old client or new client can be null!");
+ }
+
+ /**
+ * Get all clients in the system
+ */
+ @Override
+ public Collection getAllClients() {
+ return clientRepository.getAllClients();
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java b/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java
new file mode 100644
index 000000000..3b71b7318
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java
@@ -0,0 +1,319 @@
+/**
+ *
+ */
+package org.mitre.oauth2.service.impl;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntityFactory;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntityFactory;
+import org.mitre.oauth2.repository.OAuth2TokenRepository;
+import org.mitre.oauth2.service.ClientDetailsEntityService;
+import org.mitre.oauth2.service.OAuth2TokenEntityService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
+import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.security.oauth2.provider.client.ClientAuthenticationToken;
+import org.springframework.stereotype.Service;
+
+import com.google.common.collect.Sets;
+
+
+/**
+ * @author jricher
+ *
+ */
+@Service
+public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityService {
+
+ private static Logger logger = LoggerFactory.getLogger(DefaultOAuth2ProviderTokenService.class);
+
+ @Autowired
+ private OAuth2TokenRepository tokenRepository;
+
+ @Autowired
+ private ClientDetailsEntityService clientDetailsService;
+
+ @Autowired
+ private OAuth2AccessTokenEntityFactory accessTokenFactory;
+
+ @Autowired
+ private OAuth2RefreshTokenEntityFactory refreshTokenFactory;
+
+ @Override
+ public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) throws AuthenticationException, InvalidClientException {
+ if (authentication != null &&
+ authentication.getAuthorizationRequest() != null) {
+ // look up our client
+ AuthorizationRequest clientAuth = authentication.getAuthorizationRequest();
+
+ ClientDetailsEntity client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
+
+ if (client == null) {
+ throw new InvalidClientException("Client not found: " + clientAuth.getClientId());
+ }
+
+ OAuth2AccessTokenEntity token = accessTokenFactory.createNewAccessToken();
+
+ // attach the client
+ token.setClient(client);
+
+ // inherit the scope from the auth
+ // this lets us match which scope is requested
+ if (client.isScoped()) {
+
+ // restrict granted scopes to a valid subset of those
+ Set validScopes = Sets.newHashSet();
+
+ for (String requested : clientAuth.getScope()) {
+ if (client.getScope().contains(requested)) {
+ validScopes.add(requested);
+ } else {
+ logger.warn("Client " + client.getClientId() + " requested out of permission scope: " + requested);
+ }
+ }
+
+ token.setScope(validScopes);
+ }
+
+ // make it expire if necessary
+ if (client.getAccessTokenTimeout() != null) {
+ Date expiration = new Date(System.currentTimeMillis() + (client.getAccessTokenTimeout() * 1000L));
+ token.setExpiration(expiration);
+ }
+
+ // attach the authorization so that we can look it up later
+ token.setAuthentication(authentication);
+
+ // attach a refresh token, if this client is allowed to request them
+ if (client.isAllowRefresh()) {
+ OAuth2RefreshTokenEntity refreshToken = refreshTokenFactory.createNewRefreshToken();
+
+ // make it expire if necessary
+ if (client.getRefreshTokenTimeout() != null) {
+ Date expiration = new Date(System.currentTimeMillis() + (client.getRefreshTokenTimeout() * 1000L));
+ refreshToken.setExpiration(expiration);
+ }
+
+ // save our scopes so that we can reuse them later for more auth tokens
+ // TODO: save the auth instead of the just the scope?
+ if (client.isScoped()) {
+ refreshToken.setScope(clientAuth.getScope());
+ }
+
+ tokenRepository.saveRefreshToken(refreshToken);
+
+ token.setRefreshToken(refreshToken);
+ }
+
+ tokenRepository.saveAccessToken(token);
+
+ return token;
+ }
+
+ throw new AuthenticationCredentialsNotFoundException("No authentication credentials found");
+ }
+
+ @Override
+ public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, Set scope) throws AuthenticationException {
+
+ OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenByValue(refreshTokenValue);
+
+ if (refreshToken == null) {
+ throw new InvalidTokenException("Invalid refresh token: " + refreshTokenValue);
+ }
+
+ ClientDetailsEntity client = refreshToken.getClient();
+
+ //Make sure this client allows access token refreshing
+ if (!client.isAllowRefresh()) {
+ throw new InvalidClientException("Client does not allow refreshing access token!");
+ }
+
+ // clear out any access tokens
+ // TODO: make this a configurable option
+ tokenRepository.clearAccessTokensForRefreshToken(refreshToken);
+
+ if (refreshToken.isExpired()) {
+ tokenRepository.removeRefreshToken(refreshToken);
+ throw new InvalidTokenException("Expired refresh token: " + refreshTokenValue);
+ }
+
+ // TODO: have the option to recycle the refresh token here, too
+ // for now, we just reuse it as long as it's valid, which is the original intent
+
+ OAuth2AccessTokenEntity token = accessTokenFactory.createNewAccessToken();
+
+
+ if (scope != null && !scope.isEmpty()) {
+ // ensure a proper subset of scopes
+ if (refreshToken.getScope() != null && refreshToken.getScope().containsAll(scope)) {
+ // set the scope of the new access token if requested
+ refreshToken.setScope(scope);
+ } else {
+ // up-scoping is not allowed
+ // (TODO: should this throw InvalidScopeException? For now just pass through)
+ token.setScope(refreshToken.getScope());
+ }
+ } else {
+ // otherwise inherit the scope of the refresh token (if it's there -- this can return a null scope set)
+ token.setScope(refreshToken.getScope());
+ }
+
+ token.setClient(client);
+
+ if (client.getAccessTokenTimeout() != null) {
+ Date expiration = new Date(System.currentTimeMillis() + (client.getAccessTokenTimeout() * 1000L));
+ token.setExpiration(expiration);
+ }
+
+ token.setRefreshToken(refreshToken);
+
+ tokenRepository.saveAccessToken(token);
+
+ return token;
+
+ }
+
+ @Override
+ public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException {
+
+ OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenByValue(accessTokenValue);
+
+ if (accessToken == null) {
+ throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
+ }
+
+ if (accessToken.isExpired()) {
+ tokenRepository.removeAccessToken(accessToken);
+ throw new InvalidTokenException("Expired access token: " + accessTokenValue);
+ }
+
+ return accessToken.getAuthentication();
+ }
+
+
+ @Override
+ public OAuth2AccessTokenEntity getAccessToken(String accessTokenValue) throws AuthenticationException {
+ OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenByValue(accessTokenValue);
+ if (accessToken == null) {
+ throw new InvalidTokenException("Access token for value " + accessTokenValue + " was not found");
+ }
+ else {
+ return accessToken;
+ }
+ }
+
+ @Override
+ public OAuth2RefreshTokenEntity getRefreshToken(String refreshTokenValue) throws AuthenticationException {
+ OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenByValue(refreshTokenValue);
+ if (refreshToken == null) {
+ throw new InvalidTokenException("Refresh token for value " + refreshTokenValue + " was not found");
+ }
+ else {
+ return refreshToken;
+ }
+ }
+
+ @Override
+ public void revokeRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
+ tokenRepository.clearAccessTokensForRefreshToken(refreshToken);
+ tokenRepository.removeRefreshToken(refreshToken);
+ }
+
+ @Override
+ public void revokeAccessToken(OAuth2AccessTokenEntity accessToken) {
+ tokenRepository.removeAccessToken(accessToken);
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.mitre.oauth2.service.OAuth2TokenEntityService#getAccessTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity)
+ */
+ @Override
+ public List getAccessTokensForClient(ClientDetailsEntity client) {
+ return tokenRepository.getAccessTokensForClient(client);
+ }
+
+ /* (non-Javadoc)
+ * @see org.mitre.oauth2.service.OAuth2TokenEntityService#getRefreshTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity)
+ */
+ @Override
+ public List getRefreshTokensForClient(ClientDetailsEntity client) {
+ return tokenRepository.getRefreshTokensForClient(client);
+ }
+
+ @Override
+ @Scheduled(fixedRate = 5 * 60 * 1000) // schedule this task every five minutes
+ public void clearExpiredTokens() {
+ logger.info("Cleaning out all expired tokens");
+
+ List accessTokens = tokenRepository.getExpiredAccessTokens();
+ logger.info("Found " + accessTokens.size() + " expired access tokens");
+ for (OAuth2AccessTokenEntity oAuth2AccessTokenEntity : accessTokens) {
+ revokeAccessToken(oAuth2AccessTokenEntity);
+ }
+
+ List refreshTokens = tokenRepository.getExpiredRefreshTokens();
+ logger.info("Found " + refreshTokens.size() + " expired refresh tokens");
+ for (OAuth2RefreshTokenEntity oAuth2RefreshTokenEntity : refreshTokens) {
+ revokeRefreshToken(oAuth2RefreshTokenEntity);
+ }
+ }
+
+ /**
+ * Get a builder object for this class (for tests)
+ * @return
+ */
+ public static DefaultOAuth2ProviderTokenServicesBuilder makeBuilder() {
+ return new DefaultOAuth2ProviderTokenServicesBuilder();
+ }
+
+ /**
+ * Builder class for test harnesses.
+ */
+ public static class DefaultOAuth2ProviderTokenServicesBuilder {
+ private DefaultOAuth2ProviderTokenService instance;
+
+ private DefaultOAuth2ProviderTokenServicesBuilder() {
+ instance = new DefaultOAuth2ProviderTokenService();
+ }
+
+ public DefaultOAuth2ProviderTokenServicesBuilder setTokenRepository(OAuth2TokenRepository tokenRepository) {
+ instance.tokenRepository = tokenRepository;
+ return this;
+ }
+
+ public DefaultOAuth2ProviderTokenServicesBuilder setClientDetailsService(ClientDetailsEntityService clientDetailsService) {
+ instance.clientDetailsService = clientDetailsService;
+ return this;
+ }
+
+ public DefaultOAuth2ProviderTokenServicesBuilder setAccessTokenFactory(OAuth2AccessTokenEntityFactory accessTokenFactory) {
+ instance.accessTokenFactory = accessTokenFactory;
+ return this;
+ }
+
+ public DefaultOAuth2ProviderTokenServicesBuilder setRefreshTokenFactory(OAuth2RefreshTokenEntityFactory refreshTokenFactory) {
+ instance.refreshTokenFactory = refreshTokenFactory;
+ return this;
+ }
+
+ public OAuth2TokenEntityService finish() {
+ return instance;
+ }
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java b/server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java
new file mode 100644
index 000000000..e47490c39
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java
@@ -0,0 +1,43 @@
+package org.mitre.oauth2.web;
+
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.service.OAuth2TokenEntityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.servlet.ModelAndView;
+
+@Controller
+public class IntrospectionEndpoint {
+
+ @Autowired
+ OAuth2TokenEntityService tokenServices;
+
+ public IntrospectionEndpoint() {
+
+ }
+
+ public IntrospectionEndpoint(OAuth2TokenEntityService tokenServices) {
+ this.tokenServices = tokenServices;
+ }
+
+ // TODO
+ @RequestMapping("/oauth/verify")
+ public ModelAndView verify(@RequestParam("token") String tokenValue,
+ ModelAndView modelAndView) {
+ OAuth2AccessTokenEntity token = tokenServices.getAccessToken(tokenValue);
+
+ if (token == null) {
+ // if it's not a valid token, we'll print a 404
+ modelAndView.setViewName("tokenNotFound");
+ } else {
+ // if it's a valid token, we'll print out the scope and expiration
+ modelAndView.setViewName("tokenIntrospection");
+ modelAndView.addObject("entity", token);
+ }
+
+ return modelAndView;
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/web/OAuthClientAPI.java b/server/src/main/java/org/mitre/oauth2/web/OAuthClientAPI.java
new file mode 100644
index 000000000..8182df153
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/web/OAuthClientAPI.java
@@ -0,0 +1,201 @@
+package org.mitre.oauth2.web;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.mitre.oauth2.exception.ClientNotFoundException;
+import org.mitre.oauth2.exception.DuplicateClientIdException;
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.service.ClientDetailsEntityService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.GrantedAuthorityImpl;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.servlet.ModelAndView;
+
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+@Controller
+@RequestMapping("/manager/oauth/clients/api")
+public class OAuthClientAPI {
+
+ @Autowired
+ private ClientDetailsEntityService clientService;
+
+ private static final Logger logger = LoggerFactory.getLogger(OAuthClientAPI.class);
+
+ public OAuthClientAPI() {
+
+ }
+
+ public OAuthClientAPI(ClientDetailsEntityService clientService) {
+ this.clientService = clientService;
+ }
+
+ // TODO: i think this needs a fancier binding than just strings on the way in
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping("/add")
+ public ModelAndView apiAddClient(ModelAndView modelAndView,
+ @RequestParam String clientId, @RequestParam String clientSecret,
+ @RequestParam String scope, // space delimited
+ @RequestParam String grantTypes, // space delimited
+ @RequestParam(required=false) String redirectUri,
+ @RequestParam String authorities, // space delimited
+ @RequestParam(required=false) String resourceIds, // space delimited
+ @RequestParam(required=false) String name,
+ @RequestParam(required=false) String description,
+ @RequestParam(required=false, defaultValue="false") boolean allowRefresh,
+ @RequestParam(required=false) Long accessTokenTimeout,
+ @RequestParam(required=false) Long refreshTokenTimeout,
+ @RequestParam(required=false) String owner
+ ) {
+ logger.info("apiAddClient - start");
+ ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId);
+ if (oldClient != null) {
+ throw new DuplicateClientIdException(clientId);
+ }
+
+ Splitter spaceDelimited = Splitter.on(" ");
+ // parse all of our space-delimited lists
+ Set scopeSet = Sets.newHashSet(spaceDelimited.split(scope));
+ Set grantTypesSet = Sets.newHashSet(spaceDelimited.split(grantTypes)); // TODO: make a stronger binding to GrantTypes
+ logger.info("apiAddClient - before creating authorities list");
+ Set authoritiesSet = Sets.newHashSet(
+ Iterables.transform(spaceDelimited.split(authorities), new Function() {
+ @Override
+ public GrantedAuthority apply(String auth) {
+ return new GrantedAuthorityImpl(auth);
+ }
+ }));
+ logger.info("apiAddClient - printing client details");
+ logger.info("Making call to create client with " + clientId + ", " + clientSecret
+ + ", " + scopeSet + ", " + grantTypesSet + ", " + redirectUri + ", "
+ + authoritiesSet + ", " + name + ", " + description + ", " + allowRefresh
+ + ", " + accessTokenTimeout + ", " + refreshTokenTimeout + ", " + owner);
+
+ Set resourceIdSet = Sets.newHashSet(spaceDelimited.split(resourceIds));
+
+ ClientDetailsEntity client = clientService.createClient(clientId, clientSecret,
+ scopeSet, grantTypesSet, redirectUri, authoritiesSet, resourceIdSet, name, description,
+ allowRefresh, accessTokenTimeout, refreshTokenTimeout, owner);
+ logger.info("apiAddClient - adding model objects");
+ modelAndView.addObject("entity", client);
+ modelAndView.setViewName("jsonOAuthClientView");
+ logger.info("apiAddClient - end");
+ return modelAndView;
+ }
+
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping("/delete")
+ public ModelAndView apiDeleteClient(ModelAndView modelAndView,
+ @RequestParam String clientId) {
+
+ ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+
+ if (client == null) {
+ throw new ClientNotFoundException("Client not found: " + clientId);
+ }
+
+ clientService.deleteClient(client);
+
+ modelAndView.setViewName("management/successfullyRemoved");
+ return modelAndView;
+ }
+
+ // TODO: the serializtion of this falls over, don't know why
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping("/getAll")
+ public ModelAndView apiGetAllClients(ModelAndView modelAndView) {
+
+ Collection clients = clientService.getAllClients();
+ modelAndView.addObject("entity", clients);
+ modelAndView.setViewName("jsonOAuthClientView");
+
+ return modelAndView;
+ }
+
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping("/update")
+ public ModelAndView apiUpdateClient(ModelAndView modelAndView,
+ @RequestParam String clientId, @RequestParam String clientSecret,
+ @RequestParam String scope, // space delimited
+ @RequestParam String grantTypes, // space delimited
+ @RequestParam(required=false) String redirectUri,
+ @RequestParam String authorities, // space delimited
+ @RequestParam(required=false) String resourceIds, // space delimited
+ @RequestParam(required=false) String name,
+ @RequestParam(required=false) String description,
+ @RequestParam(required=false, defaultValue="false") boolean allowRefresh,
+ @RequestParam(required=false) Long accessTokenTimeout,
+ @RequestParam(required=false) Long refreshTokenTimeout,
+ @RequestParam(required=false) String owner
+ ) {
+ ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+
+ if (client == null) {
+ throw new ClientNotFoundException("Client not found: " + clientId);
+ }
+
+ Splitter spaceDelimited = Splitter.on(" ");
+ // parse all of our space-delimited lists
+ Set scopeSet = Sets.newHashSet(spaceDelimited.split(scope));
+ Set grantTypesSet = Sets.newHashSet(spaceDelimited.split(grantTypes)); // TODO: make a stronger binding to GrantTypes
+ Set authoritiesSet = Sets.newHashSet(
+ Iterables.transform(spaceDelimited.split(authorities), new Function() {
+ @Override
+ public GrantedAuthority apply(String auth) {
+ return new GrantedAuthorityImpl(auth);
+ }
+ }));
+ Set resourceIdSet = Sets.newHashSet(spaceDelimited.split(resourceIds));
+
+
+ client.setClientSecret(clientSecret);
+ client.setScope(scopeSet);
+ client.setAuthorizedGrantTypes(grantTypesSet);
+ client.setRegisteredRedirectUri(redirectUri);
+ client.setAuthorities(authoritiesSet);
+ client.setResourceIds(resourceIdSet);
+ client.setClientName(name);
+ client.setClientDescription(description);
+ client.setAllowRefresh(allowRefresh);
+ client.setAccessTokenTimeout(accessTokenTimeout);
+ client.setRefreshTokenTimeout(refreshTokenTimeout);
+ client.setOwner(owner);
+
+ clientService.updateClient(client, client);
+
+ modelAndView.addObject("entity", client);
+ modelAndView.setViewName("jsonOAuthClientView");
+
+ return modelAndView;
+ }
+
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping("/getById")
+ public ModelAndView getClientById(ModelAndView modelAndView,
+ @RequestParam String clientId) {
+
+ ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+
+ if (client == null) {
+ throw new ClientNotFoundException("Client not found: " + clientId);
+ }
+
+ modelAndView.addObject("entity", client);
+ modelAndView.setViewName("jsonOAuthClientView");
+
+ return modelAndView;
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/web/OAuthClientController.java b/server/src/main/java/org/mitre/oauth2/web/OAuthClientController.java
new file mode 100644
index 000000000..f96f67531
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/web/OAuthClientController.java
@@ -0,0 +1,166 @@
+/**
+ *
+ */
+package org.mitre.oauth2.web;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+import org.mitre.oauth2.service.ClientDetailsEntityService;
+import org.mitre.oauth2.service.OAuth2TokenEntityService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.GrantedAuthorityImpl;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+import com.google.common.collect.Sets;
+
+
+/**
+ *
+ * Endpoint for managing OAuth2 clients
+ *
+ * @author jricher
+ *
+ */
+@Controller
+@RequestMapping("/manager/oauth/clients")
+public class OAuthClientController {
+
+ private final static Set GRANT_TYPES = Sets.newHashSet("authorization_code", "client_credentials", "password", "implicit");
+
+ @Autowired
+ private ClientDetailsEntityService clientService;
+
+ @Autowired
+ private OAuth2TokenEntityService tokenService;
+
+ private Logger logger;
+
+ public OAuthClientController() {
+ logger = LoggerFactory.getLogger(this.getClass());
+ }
+
+ public OAuthClientController(ClientDetailsEntityService clientService, OAuth2TokenEntityService tokenService) {
+ this.clientService = clientService;
+ this.tokenService = tokenService;
+ logger = LoggerFactory.getLogger(this.getClass());
+ }
+
+ /**
+ * Redirect to the "/" version of the root
+ * @param modelAndView
+ * @return
+ */
+ @RequestMapping("")
+ public ModelAndView redirectRoot(ModelAndView modelAndView) {
+ modelAndView.setViewName("redirect:/manager/oauth/clients/");
+ return modelAndView;
+ }
+
+ /**
+ * View all clients
+ * @param modelAndView
+ * @return
+ */
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping("/")
+ public ModelAndView viewAllClients(ModelAndView modelAndView) {
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ //ClientAuthenticationToken clientAuth = (ClientAuthenticationToken) ((OAuth2Authentication) auth).getClientAuthentication();
+ AuthorizationRequest clientAuth = ((OAuth2Authentication) auth).getAuthorizationRequest();
+
+ logger.info("Client auth = " + clientAuth);
+ logger.info("Granted authorities = " + clientAuth.getAuthorities().toString());
+
+ Collection clients = clientService.getAllClients();
+ modelAndView.addObject("clients", clients);
+ modelAndView.setViewName("/management/oauth/clientIndex");
+
+ return modelAndView;
+ }
+
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping("/add")
+ public ModelAndView redirectAdd(ModelAndView modelAndView) {
+ modelAndView.setViewName("redirect:/manager/oauth/clients/add/");
+ return modelAndView;
+ }
+
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping("/add/")
+ public ModelAndView addClientPage(ModelAndView modelAndView) {
+
+ Set auth = Sets.newHashSet();
+ auth.add(new GrantedAuthorityImpl("ROLE_CLIENT"));
+
+ ClientDetailsEntity client = ClientDetailsEntity.makeBuilder()
+ .setScope(Sets.newHashSet("scope"))
+ .setAuthorities(auth) // why do we have to pull this into a separate list?
+ .setAuthorizedGrantTypes(Sets.newHashSet("authorization_code"))
+ .finish();
+ modelAndView.addObject("availableGrantTypes", GRANT_TYPES);
+ modelAndView.addObject("client", client);
+
+ modelAndView.setViewName("/management/oauth/editClient");
+ return modelAndView;
+ }
+
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping("/delete/{clientId}")
+ public ModelAndView deleteClientConfirmation(ModelAndView modelAndView,
+ @PathVariable String clientId) {
+
+ ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+ modelAndView.addObject("client", client);
+ modelAndView.setViewName("/management/oauth/deleteClientConfirm");
+
+ return modelAndView;
+ }
+
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping("/edit/{clientId}")
+ public ModelAndView editClientPage(ModelAndView modelAndView,
+ @PathVariable String clientId) {
+
+ ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+
+ modelAndView.addObject("availableGrantTypes", GRANT_TYPES);
+ modelAndView.addObject("client", client);
+ modelAndView.setViewName("/management/oauth/editClient");
+
+ return modelAndView;
+ }
+
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping("/view/{clientId}")
+ public ModelAndView viewClientDetails(ModelAndView modelAndView,
+ @PathVariable String clientId) {
+
+ ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+
+ List accessTokens = tokenService.getAccessTokensForClient(client);
+ List refreshTokens = tokenService.getRefreshTokensForClient(client);
+
+ modelAndView.addObject("client", client);
+ modelAndView.addObject("accessTokens", accessTokens);
+ modelAndView.addObject("refreshTokens", refreshTokens);
+
+ modelAndView.setViewName("/management/oauth/viewClient");
+ return modelAndView;
+ }
+}
diff --git a/server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java b/server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java
new file mode 100644
index 000000000..f11b509c8
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java
@@ -0,0 +1,70 @@
+/**
+ *
+ */
+package org.mitre.oauth2.web;
+
+import org.mitre.oauth2.exception.ClientNotFoundException;
+import org.mitre.oauth2.service.ClientDetailsEntityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.ClientDetails;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.SessionAttributes;
+import org.springframework.web.servlet.ModelAndView;
+
+/**
+ * @author jricher
+ *
+ */
+@Controller
+@SessionAttributes(types = AuthorizationRequest.class)
+public class OAuthConfirmationController {
+
+ private ClientDetailsEntityService clientService;
+
+ public OAuthConfirmationController() {
+
+ }
+
+ public OAuthConfirmationController(ClientDetailsEntityService clientService) {
+ this.clientService = clientService;
+ }
+
+ @PreAuthorize("hasRole('ROLE_USER')")
+ @RequestMapping("/oauth/user/approve")
+ public ModelAndView confimAccess(@ModelAttribute AuthorizationRequest clientAuth,
+ ModelAndView modelAndView) {
+
+ ClientDetails client = clientService.loadClientByClientId(clientAuth.getClientId());
+
+ if (client == null) {
+ throw new ClientNotFoundException("Client not found: " + clientAuth.getClientId());
+ }
+
+ modelAndView.addObject("auth_request", clientAuth);
+ modelAndView.addObject("client", client);
+ modelAndView.setViewName("oauth/approve");
+
+ return modelAndView;
+ }
+
+ /**
+ * @return the clientService
+ */
+ public ClientDetailsEntityService getClientService() {
+ return clientService;
+ }
+
+ /**
+ * @param clientService the clientService to set
+ */
+ @Autowired
+ public void setClientService(ClientDetailsEntityService clientService) {
+ this.clientService = clientService;
+ }
+
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java b/server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java
new file mode 100644
index 000000000..324c94f0e
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java
@@ -0,0 +1,79 @@
+package org.mitre.oauth2.web;
+
+import org.mitre.oauth2.exception.PermissionDeniedException;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+import org.mitre.oauth2.service.OAuth2TokenEntityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.servlet.ModelAndView;
+
+@Controller
+public class RevocationEndpoint {
+ @Autowired
+ OAuth2TokenEntityService tokenServices;
+
+ public RevocationEndpoint() {
+
+ }
+
+ public RevocationEndpoint(OAuth2TokenEntityService tokenServices) {
+ this.tokenServices = tokenServices;
+ }
+
+ // TODO
+ @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
+ @RequestMapping("/oauth/revoke")
+ public ModelAndView revoke(@RequestParam("token") String tokenValue,
+ ModelAndView modelAndView) {
+
+ OAuth2RefreshTokenEntity refreshToken = tokenServices.getRefreshToken(tokenValue);
+ OAuth2AccessTokenEntity accessToken = tokenServices.getAccessToken(tokenValue);
+
+ if (refreshToken == null && accessToken == null) {
+ // TODO: this should throw a 400 with a JSON error code
+ throw new InvalidTokenException("Invalid OAuth token: " + tokenValue);
+ }
+
+ // TODO: there should be a way to do this in SPEL, right?
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ if (auth instanceof OAuth2Authentication) {
+ // we've got a client acting on its own behalf, not an admin
+ //ClientAuthentication clientAuth = (ClientAuthenticationToken) ((OAuth2Authentication) auth).getClientAuthentication();
+ AuthorizationRequest clientAuth = ((OAuth2Authentication) auth).getAuthorizationRequest();
+
+ if (refreshToken != null) {
+ if (!refreshToken.getClient().getClientId().equals(clientAuth.getClientId())) {
+ // trying to revoke a token we don't own, fail
+ // TODO: this should throw a 403
+ throw new PermissionDeniedException("Client tried to revoke a token it doesn't own");
+ }
+ } else {
+ if (!accessToken.getClient().getClientId().equals(clientAuth.getClientId())) {
+ // trying to revoke a token we don't own, fail
+ // TODO: this should throw a 403
+ throw new PermissionDeniedException("Client tried to revoke a token it doesn't own");
+ }
+ }
+ }
+
+ // if we got this far, we're allowed to do this
+ if (refreshToken != null) {
+ tokenServices.revokeRefreshToken(refreshToken);
+ } else {
+ tokenServices.revokeAccessToken(accessToken);
+ }
+
+ // TODO: throw a 200 back (no content?)
+ return modelAndView;
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/openid/connect/model/Address.java b/server/src/main/java/org/mitre/openid/connect/model/Address.java
index f74b968e6..9fefc09b8 100644
--- a/server/src/main/java/org/mitre/openid/connect/model/Address.java
+++ b/server/src/main/java/org/mitre/openid/connect/model/Address.java
@@ -1,10 +1,14 @@
package org.mitre.openid.connect.model;
import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
@Entity
public class Address {
+ private Long id;
private String formatted;
private String street_address;
private String locality;
@@ -12,6 +16,8 @@ public class Address {
private String postal_code;
private String country;
+
+
/**
* @return the formatted
*/
@@ -84,5 +90,21 @@ public class Address {
public void setCountry(String country) {
this.country = country;
}
+
+ /**
+ * @return the id
+ */
+ @Id
+ @GeneratedValue(strategy=GenerationType.IDENTITY)
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId(Long id) {
+ this.id = id;
+ }
}
diff --git a/server/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java b/server/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java
index 3f16c4d03..4b6e4682a 100644
--- a/server/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java
+++ b/server/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java
@@ -8,6 +8,8 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
import javax.persistence.Temporal;
import org.springframework.security.oauth2.provider.ClientDetails;
@@ -48,7 +50,7 @@ public class ApprovedSite {
* @return the id
*/
@Id
- @GeneratedValue(strategy = GenerationType.AUTO)
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
@@ -63,6 +65,7 @@ public class ApprovedSite {
/**
* @return the userInfo
*/
+ @ManyToOne
public UserInfo getUserInfo() {
return userInfo;
}
@@ -123,6 +126,7 @@ public class ApprovedSite {
/**
* @return the allowedScopes
*/
+ @OneToMany
public Collection getAllowedScopes() {
return allowedScopes;
}
diff --git a/server/src/main/java/org/mitre/openid/connect/model/Event.java b/server/src/main/java/org/mitre/openid/connect/model/Event.java
index d287ad996..bc776c423 100644
--- a/server/src/main/java/org/mitre/openid/connect/model/Event.java
+++ b/server/src/main/java/org/mitre/openid/connect/model/Event.java
@@ -2,7 +2,12 @@ package org.mitre.openid.connect.model;
import java.util.Date;
+import javax.persistence.Basic;
import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Temporal;
/**
* Class to contain a logged event in the system.
@@ -19,5 +24,46 @@ public class Event {
private Long id;
private EventType type;
private Date timestamp;
+
+ /**
+ * @return the id
+ */
+ @Id
+ @GeneratedValue(strategy=GenerationType.IDENTITY)
+ public Long getId() {
+ return id;
+ }
+ /**
+ * @param id the id to set
+ */
+ public void setId(Long id) {
+ this.id = id;
+ }
+ /**
+ * @return the type
+ */
+ public EventType getType() {
+ return type;
+ }
+ /**
+ * @param type the type to set
+ */
+ public void setType(EventType type) {
+ this.type = type;
+ }
+ /**
+ * @return the timestamp
+ */
+ @Basic
+ @Temporal(javax.persistence.TemporalType.TIMESTAMP)
+ public Date getTimestamp() {
+ return timestamp;
+ }
+ /**
+ * @param timestamp the timestamp to set
+ */
+ public void setTimestamp(Date timestamp) {
+ this.timestamp = timestamp;
+ }
}
diff --git a/server/src/main/java/org/mitre/openid/connect/model/IdToken.java b/server/src/main/java/org/mitre/openid/connect/model/IdToken.java
deleted file mode 100644
index 5a08edb4f..000000000
--- a/server/src/main/java/org/mitre/openid/connect/model/IdToken.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.mitre.openid.connect.model;
-
-import javax.persistence.Entity;
-
-import org.mitre.jwt.model.Jwt;
-
-/*
- * TODO: This class needs to be encoded as a JWT
- */
-@Entity
-public class IdToken extends Jwt {
-
- private String iss;
- private String user_id;
- private String aud;
- private String exp;
- private String iso29115;
- private String nonce;
- private String auth_time;
- /**
- * @return the iss
- */
- public String getIss() {
- return iss;
- }
- /**
- * @param iss the iss to set
- */
- public void setIss(String iss) {
- this.iss = iss;
- }
- /**
- * @return the user_id
- */
- public String getUser_id() {
- return user_id;
- }
- /**
- * @param user_id the user_id to set
- */
- public void setUser_id(String user_id) {
- this.user_id = user_id;
- }
- /**
- * @return the aud
- */
- public String getAud() {
- return aud;
- }
- /**
- * @param aud the aud to set
- */
- public void setAud(String aud) {
- this.aud = aud;
- }
- /**
- * @return the exp
- */
- public String getExp() {
- return exp;
- }
- /**
- * @param exp the exp to set
- */
- public void setExp(String exp) {
- this.exp = exp;
- }
- /**
- * @return the iso29115
- */
- public String getIso29115() {
- return iso29115;
- }
- /**
- * @param iso29115 the iso29115 to set
- */
- public void setIso29115(String iso29115) {
- this.iso29115 = iso29115;
- }
- /**
- * @return the nonce
- */
- public String getNonce() {
- return nonce;
- }
- /**
- * @param nonce the nonce to set
- */
- public void setNonce(String nonce) {
- this.nonce = nonce;
- }
- /**
- * @return the auth_time
- */
- public String getAuth_time() {
- return auth_time;
- }
- /**
- * @param auth_time the auth_time to set
- */
- public void setAuth_time(String auth_time) {
- this.auth_time = auth_time;
- }
-
-}
diff --git a/server/src/main/java/org/mitre/openid/connect/model/IdTokenClaims.java b/server/src/main/java/org/mitre/openid/connect/model/IdTokenClaims.java
new file mode 100644
index 000000000..23c2f2e57
--- /dev/null
+++ b/server/src/main/java/org/mitre/openid/connect/model/IdTokenClaims.java
@@ -0,0 +1,77 @@
+package org.mitre.openid.connect.model;
+
+import java.util.Date;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+import org.mitre.jwt.model.Jwt;
+import org.mitre.jwt.model.JwtClaims;
+
+/*
+ * TODO: This class needs to be encoded as a JWT
+ */
+@Entity
+public class IdTokenClaims extends JwtClaims {
+
+ public static final String USER_ID = "user_id";
+ public static final String AUTHENTICATION_CONTEXT_CLASS_REFERENCE = "acr";
+ public static final String NONCE = "nonce";
+ public static final String AUTH_TIME = "auth_time";
+
+ private Long id;
+
+ /**
+ * @return the id
+ */
+ @Id
+ @GeneratedValue(strategy=GenerationType.IDENTITY)
+ public Long getId() {
+ return id;
+ }
+ /**
+ * @param id the id to set
+ */
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+
+ public String getUserId() {
+ return getClaimAsString(USER_ID);
+ }
+
+ public void setUserId(String user_id) {
+ setClaim(USER_ID, user_id);
+ }
+
+
+ public String getAuthContext() {
+ return getClaimAsString(AUTHENTICATION_CONTEXT_CLASS_REFERENCE);
+ }
+
+ public void setAuthContext(String acr) {
+ setClaim(AUTHENTICATION_CONTEXT_CLASS_REFERENCE, acr);
+ }
+
+
+ public String getNonce() {
+ return getClaimAsString(NONCE);
+ }
+
+ public void setNonce(String nonce) {
+ setClaim(NONCE, nonce);
+ }
+
+
+ public Date getAuthTime() {
+ return getClaimAsDate(AUTH_TIME);
+ }
+
+ public void setAuthTime(Date authTime) {
+ setClaim(AUTH_TIME, authTime);
+ }
+
+}
diff --git a/server/src/main/java/org/mitre/openid/connect/model/UserInfo.java b/server/src/main/java/org/mitre/openid/connect/model/UserInfo.java
index 67c8a2687..acbebd898 100644
--- a/server/src/main/java/org/mitre/openid/connect/model/UserInfo.java
+++ b/server/src/main/java/org/mitre/openid/connect/model/UserInfo.java
@@ -1,6 +1,7 @@
package org.mitre.openid.connect.model;
import javax.persistence.Entity;
+import javax.persistence.Id;
@Entity
public class UserInfo {
@@ -29,6 +30,7 @@ public class UserInfo {
/**
* @return the user_id
*/
+ @Id
public String getUser_id() {
return user_id;
}
diff --git a/server/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java b/server/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java
index ca5b4987d..019988888 100644
--- a/server/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java
+++ b/server/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java
@@ -6,6 +6,7 @@ import java.util.Date;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
+import javax.persistence.ManyToOne;
import org.springframework.security.oauth2.provider.ClientDetails;
@@ -19,7 +20,7 @@ public class WhitelistedSite {
// unique id
@Id
- @GeneratedValue(strategy = GenerationType.AUTO)
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// who added this site to the whitelist (should be an admin)
@@ -30,5 +31,6 @@ public class WhitelistedSite {
// what scopes be allowed by default
// this should include all information for what data to access
+ @ManyToOne
private Collection allowedScopes;
}
diff --git a/server/src/main/java/org/mitre/openid/connect/repository/IdTokenRepository.java b/server/src/main/java/org/mitre/openid/connect/repository/IdTokenRepository.java
index 98676bb66..e67e7c597 100644
--- a/server/src/main/java/org/mitre/openid/connect/repository/IdTokenRepository.java
+++ b/server/src/main/java/org/mitre/openid/connect/repository/IdTokenRepository.java
@@ -1,11 +1,11 @@
package org.mitre.openid.connect.repository;
-import org.mitre.openid.connect.model.IdToken;
+import org.mitre.openid.connect.model.IdTokenClaims;
public interface IdTokenRepository {
- public IdToken getById(Long id);
+ public IdTokenClaims getById(Long id);
- public IdToken save(IdToken idToken);
+ public IdTokenClaims save(IdTokenClaims idToken);
}
diff --git a/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java b/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java
index 935509d85..1430acef9 100644
--- a/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java
+++ b/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java
@@ -1,6 +1,6 @@
package org.mitre.openid.connect.web;
-import org.mitre.openid.connect.model.IdToken;
+import org.mitre.openid.connect.model.IdTokenClaims;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -13,7 +13,7 @@ public class CheckIDEndpoint {
@RequestMapping("/")
public ModelAndView checkID(@RequestParam("id_token") String idToken, ModelAndView mav) {
- IdToken token = new IdToken();
+ IdTokenClaims token = new IdTokenClaims();
//TODO: Set claims
diff --git a/server/src/main/java/org/mitre/pushee/openid/service/impl/OpenIDUserDetailsService.java b/server/src/main/java/org/mitre/pushee/openid/service/impl/OpenIDUserDetailsService.java
new file mode 100644
index 000000000..4d608930e
--- /dev/null
+++ b/server/src/main/java/org/mitre/pushee/openid/service/impl/OpenIDUserDetailsService.java
@@ -0,0 +1,98 @@
+package org.mitre.pushee.openid.service.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.GrantedAuthorityImpl;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+
+public class OpenIDUserDetailsService implements UserDetailsService {
+
+ private String openIdRoot;
+ private String admins;
+ private List adminList = new ArrayList();
+
+ private GrantedAuthority roleUser = new GrantedAuthorityImpl("ROLE_USER");
+ private GrantedAuthority roleAdmin = new GrantedAuthorityImpl("ROLE_ADMIN");
+
+ /**
+ * @return the roleUser
+ */
+ public GrantedAuthority getRoleUser() {
+ return roleUser;
+ }
+
+ /**
+ * @param roleUser the roleUser to set
+ */
+ public void setRoleUser(GrantedAuthority roleUser) {
+ this.roleUser = roleUser;
+ }
+
+ /**
+ * @return the roleAdmin
+ */
+ public GrantedAuthority getRoleAdmin() {
+ return roleAdmin;
+ }
+
+ /**
+ * @param roleAdmin the roleAdmin to set
+ */
+ public void setRoleAdmin(GrantedAuthority roleAdmin) {
+ this.roleAdmin = roleAdmin;
+ }
+
+ public String getOpenIdRoot() {
+ return openIdRoot;
+ }
+
+ public void setOpenIdRoot(String openIdRoot) {
+ this.openIdRoot = openIdRoot;
+ }
+
+ public String getAdmins() {
+ return admins;
+ }
+
+ public void setAdmins(String admins) {
+ this.admins = admins;
+ adminList.clear();
+ Iterables.addAll(adminList, Splitter.on(',').omitEmptyStrings().split(admins));
+ }
+
+ public UserDetails loadUserByUsername(String identifier) throws UsernameNotFoundException, DataAccessException {
+
+ if (identifier != null && identifier.startsWith(openIdRoot)) {
+ String username = identifier;
+ //String username = identifier.replace(openIdRoot, ""); // strip off the OpenID root
+ String password = "notused";
+ boolean enabled = true;
+ boolean accountNonExpired = true;
+ boolean credentialsNonExpired = true;
+ boolean accountNonLocked = true;
+ List authorities = new ArrayList();
+ authorities.add(roleUser);
+
+ // calculate raw user id (SUI)
+ // TODO: make this more generic, right now only works with postfix names
+ String userid = identifier.replace(openIdRoot, "");
+
+ if (adminList.contains(userid)) {
+ authorities.add(roleAdmin);
+ }
+
+ return new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
+ } else {
+ throw new UsernameNotFoundException("Identifier " + identifier + " did not match OpenID root " + openIdRoot);
+ }
+ }
+}
diff --git a/server/src/main/java/org/mitre/util/jpa/JpaUtil.java b/server/src/main/java/org/mitre/util/jpa/JpaUtil.java
new file mode 100644
index 000000000..9ce03c1b9
--- /dev/null
+++ b/server/src/main/java/org/mitre/util/jpa/JpaUtil.java
@@ -0,0 +1,37 @@
+package org.mitre.util.jpa;
+
+import org.springframework.dao.IncorrectResultSizeDataAccessException;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManager;
+import java.util.List;
+
+/**
+ * @author mfranklin
+ * Date: 4/28/11
+ * Time: 2:13 PM
+ */
+public class JpaUtil {
+ public static T getSingleResult(List list) {
+ switch(list.size()) {
+ case 0:
+ return null;
+ case 1:
+ return list.get(0);
+ default:
+ throw new IncorrectResultSizeDataAccessException(1);
+ }
+ }
+
+ public static T saveOrUpdate(I id, EntityManager entityManager, T entity) {
+ if (id == null) {
+ entityManager.persist(entity);
+ entityManager.flush();
+ return entity;
+ } else {
+ T tmp = entityManager.merge(entity);
+ entityManager.flush();
+ return tmp;
+ }
+ }
+}
diff --git a/spring-security-oauth b/spring-security-oauth
index 7a55ca546..0aa98f094 160000
--- a/spring-security-oauth
+++ b/spring-security-oauth
@@ -1 +1 @@
-Subproject commit 7a55ca546a3567a8a1833340d0ff9e2b47cf4bdc
+Subproject commit 0aa98f09467f90b36ed06ba5cdbb82722f08de26
diff --git a/upstream-patch/README.txt b/upstream-patch/README.txt
new file mode 100644
index 000000000..cf7d5d9e5
--- /dev/null
+++ b/upstream-patch/README.txt
@@ -0,0 +1 @@
+This directory contains .patch files which must be applied to the upstream spring-security-oauth2 module in order for the project to build.
diff --git a/upstream-patch/secoauth_183.patch b/upstream-patch/secoauth_183.patch
new file mode 100644
index 000000000..43e148be1
--- /dev/null
+++ b/upstream-patch/secoauth_183.patch
@@ -0,0 +1,113 @@
+diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java
+index 20d2512..a773b29 100644
+--- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java
++++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java
+@@ -9,9 +9,17 @@ public class ExpiringOAuth2RefreshToken extends OAuth2RefreshToken {
+
+ private static final long serialVersionUID = 3449554332764129719L;
+
+- private final Date expiration;
++ private Date expiration;
+
+ /**
++ * Create an expiring refresh token with a null value and no expiration
++ * @param expiration
++ */
++ public ExpiringOAuth2RefreshToken() {
++ this(null, null);
++ }
++
++ /**
+ * @param value
+ */
+ public ExpiringOAuth2RefreshToken(String value, Date expiration) {
+@@ -28,4 +36,11 @@ public class ExpiringOAuth2RefreshToken extends OAuth2RefreshToken {
+ return expiration;
+ }
+
++ /**
++ * Set the expiration of this token
++ */
++ public void setExpiration(Date expiration) {
++ this.expiration = expiration;
++ }
++
+ }
+diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java
+index 791780f..25edf77 100644
+--- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java
++++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java
+@@ -57,7 +57,7 @@ public class OAuth2AccessToken implements Serializable {
+ */
+ public static String SCOPE = "scope";
+
+- private final String value;
++ private String value;
+
+ private Date expiration;
+
+@@ -74,8 +74,10 @@ public class OAuth2AccessToken implements Serializable {
+ this.value = value;
+ }
+
+- @SuppressWarnings("unused")
+- private OAuth2AccessToken() {
++ /**
++ * Create an access token with no value
++ */
++ public OAuth2AccessToken() {
+ this(null);
+ }
+
+@@ -88,6 +90,14 @@ public class OAuth2AccessToken implements Serializable {
+ return value;
+ }
+
++ /**
++ * Set the value of the token.
++ * @param value the token value
++ */
++ public void setValue(String value) {
++ this.value = value;
++ }
++
+ public int getExpiresIn() {
+ return expiration != null ? Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
+ .intValue() : 0;
+diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java
+index 00b002c..96f3f1b 100644
+--- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java
++++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java
+@@ -15,9 +15,16 @@ public class OAuth2RefreshToken implements Serializable {
+
+ private static final long serialVersionUID = 8349970621900575838L;
+
+- private final String value;
++ private String value;
+
+ /**
++ * Create an empty token with no value
++ */
++ public OAuth2RefreshToken() {
++ this(null);
++ }
++
++ /**
+ * Create a new refresh token.
+ */
+ @JsonCreator
+@@ -35,6 +42,14 @@ public class OAuth2RefreshToken implements Serializable {
+ return value;
+ }
+
++ /**
++ * Set the value of the token
++ * @param value the value of the token
++ */
++ public void setValue(String value) {
++ this.value = value;
++ }
++
+ @Override
+ public String toString() {
+ return getValue();