Implemented OAuth2 request access token with authorization code grant.
Change-Id: Ia3c427316748876db65373b31ea453bb71f9448b
diff --git a/full/Changes b/full/Changes
index 85c1dd7..9e0be4b 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,5 +1,5 @@
version 0.60.2
-23/04/2018
+25/04/2018
- implemented OAuth2 client registration (margaretha)
- implemented OAuth2 client authentication (margaretha)
- changed virtual corpus search to retrieval (margaretha)
@@ -7,10 +7,14 @@
- added client registration and deregistration tests (margaretha)
- implemented confidential client deregistration task (margaretha)
- fixed storing client secret (margaretha)
- - implemented OAuth2 exception handler (margaretha)
+ - implemented OAuth2 response handler (margaretha)
- implemented OAuth2 request access token with client credentials grant (margaretha)
- implemented OAuth2 request access token with resource owner password grant (margaretha)
- implemented OAuth2 authorization code request (margaretha)
+ - added OAuth2 error codes (margaretha)
+ - added OAuth2 authorization, scope and access token tables for SQLite (margaretha)
+ - implemented OAuth2 authorization, scope and access token DAO (margaretha)
+ - implemented OAuth2 request access token with authorization code grant (margaretha)
version 0.60.1
28/03/2018
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/BasicAuthenticationManager.java b/full/src/main/java/de/ids_mannheim/korap/authentication/spring/BasicAuthenticationManager.java
deleted file mode 100644
index 808cbf4..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/BasicAuthenticationManager.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package de.ids_mannheim.korap.authentication.spring;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-
-import de.ids_mannheim.korap.config.Attributes;
-import de.ids_mannheim.korap.config.KustvaktConfiguration;
-import de.ids_mannheim.korap.config.Scopes;
-import de.ids_mannheim.korap.constant.TokenType;
-import de.ids_mannheim.korap.dao.UserDao;
-import de.ids_mannheim.korap.interfaces.EncryptionIface;
-import de.ids_mannheim.korap.security.context.TokenContext;
-import de.ids_mannheim.korap.user.KorAPUser;
-import de.ids_mannheim.korap.user.User;
-import de.ids_mannheim.korap.utils.TimeUtils;
-
-/** * Basic authentication manager is intended to be used with a database.
- * It is currently only used for testing using a dummy DAO (@see {@link UserDao})
- * without passwords.
- *
- * @author margaretha
- *
- */
-public class BasicAuthenticationManager implements AuthenticationManager{
-
- @Autowired
- private KustvaktConfiguration config;
- @Autowired
- private EncryptionIface crypto;
- @Autowired
- private UserDao dao;
-
- @Override
- public Authentication authenticate (Authentication authentication)
- throws AuthenticationException {
-
- String username = (String) authentication.getPrincipal();
- String password = (String) authentication.getCredentials();
-
- TokenContext c = new TokenContext();
- User user = dao.getAccount(username);
- if (user instanceof KorAPUser
- && ((KorAPUser) user).getPassword() != null) {
- boolean check = crypto.checkHash(password,
- ((KorAPUser) user).getPassword());
-
- if (!check) return null;
- }
-
- c.setUsername(username);
- c.setExpirationTime(TimeUtils.plusSeconds(this.config.getTokenTTL())
- .getMillis());
- c.setTokenType(TokenType.BASIC);
- // todo: for production mode, set true
- c.setSecureRequired(false);
- // EM: is this secure?
- c.setToken(authentication.toString());
- c.addContextParameter(Attributes.SCOPES,
- Scopes.Scope.search.toString());
-
- return authentication;
- }
-
-}
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktAuthenticationException.java b/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktAuthenticationException.java
deleted file mode 100644
index aeb7ff2..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktAuthenticationException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package de.ids_mannheim.korap.authentication.spring;
-
-import org.springframework.security.core.AuthenticationException;
-
-import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.web.CoreResponseHandler;
-
-public class KustvaktAuthenticationException extends AuthenticationException {
-
- /**
- * Auto-generated serian UID
- */
- private static final long serialVersionUID = -357703101436703635L;
- private String notification;
-
- public KustvaktAuthenticationException (String msg) {
- super(msg);
- }
-
- public KustvaktAuthenticationException (KustvaktException e) {
- super(e.getMessage());
- notification = CoreResponseHandler.buildNotification(e.getStatusCode(),
- e.getMessage(), e.getEntity());
- }
-
- public String getNotification () {
- return notification;
- }
-}
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktBasicAuthenticationEntryPoint.java b/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktBasicAuthenticationEntryPoint.java
deleted file mode 100644
index de044c0..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktBasicAuthenticationEntryPoint.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.ids_mannheim.korap.authentication.spring;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
-
-public class KustvaktBasicAuthenticationEntryPoint
- extends BasicAuthenticationEntryPoint {
-
- @Override
- public void commence (HttpServletRequest request,
- HttpServletResponse response, AuthenticationException authException)
- throws IOException, ServletException {
- super.commence(request, response, authException);
-
- String notification = ((KustvaktAuthenticationException) authException)
- .getNotification();
-
- PrintWriter writer = response.getWriter();
- writer.write(notification);
- writer.flush();
- writer.close();
- }
-}
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/LDAPAuthenticationManager.java b/full/src/main/java/de/ids_mannheim/korap/authentication/spring/LDAPAuthenticationManager.java
deleted file mode 100644
index a094d9c..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/LDAPAuthenticationManager.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package de.ids_mannheim.korap.authentication.spring;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-
-import com.unboundid.ldap.sdk.LDAPException;
-
-import de.ids_mannheim.korap.authentication.LdapAuth3;
-import de.ids_mannheim.korap.config.FullConfiguration;
-import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.exceptions.StatusCodes;
-
-public class LDAPAuthenticationManager implements AuthenticationManager {
-
- private static Logger jlog =
- LoggerFactory.getLogger(LDAPAuthenticationManager.class);
- @Autowired
- private FullConfiguration config;
-
- @Override
- public Authentication authenticate (Authentication authentication)
- throws AuthenticationException {
-
- String username = (String) authentication.getPrincipal();
- String password = (String) authentication.getCredentials();
-
- // just to make sure that the plain password does not appear anywhere in
- // the logs!
-
- System.out.printf("Debug: authenticateIdM: entering for '%s'...\n",
- username);
-
- /**
- * wozu Apache Validatoren für User/Passwort für IdM/LDAP? siehe
- * validation.properties. Abgeschaltet 21.04.17/FB try {
- * validator.validateEntry(username, Attributes.USERNAME); } catch
- * (KustvaktException e) { throw new WrappedException(e,
- * StatusCodes.LOGIN_FAILED, username); }
- */
- if (username == null || username.isEmpty() || password == null
- || password.isEmpty()) {
- throw new KustvaktAuthenticationException(
- new KustvaktException(StatusCodes.BAD_CREDENTIALS,
- "Missing username or password."));
- }
-
- // LDAP Access:
- try {
- // todo: unknown = ...
- int ret =
- LdapAuth3.login(username, password, config.getLdapConfig());
- System.out.printf(
- "Debug: autenticationIdM: Ldap.login(%s) returns: %d.\n",
- username, ret);
- if (ret != LdapAuth3.LDAP_AUTH_ROK) {
- jlog.error("LdapAuth3.login(username='{}') returns '{}'='{}'!",
- username, ret, LdapAuth3.getErrMessage(ret));
-
- // mask exception to disable user guessing in possible attacks
- /*
- * by Hanl throw new WrappedException(new
- * KustvaktException(username, StatusCodes.BAD_CREDENTIALS),
- * StatusCodes.LOGIN_FAILED, username);
- */
- throw new KustvaktAuthenticationException(new KustvaktException(
- username, StatusCodes.LDAP_BASE_ERRCODE + ret,
- LdapAuth3.getErrMessage(ret), username));
- }
- }
- catch (LDAPException e) {
-
- jlog.error("Error: username='{}' -> '{}'!", username, e);
- // mask exception to disable user guessing in possible attacks
- /*
- * by Hanl: throw new WrappedException(new
- * KustvaktException(username, StatusCodes.BAD_CREDENTIALS),
- * StatusCodes.LOGIN_FAILED, username);
- */
- throw new KustvaktAuthenticationException(new KustvaktException(
- username,
- StatusCodes.LDAP_BASE_ERRCODE + LdapAuth3.LDAP_AUTH_RINTERR,
- LdapAuth3.getErrMessage(LdapAuth3.LDAP_AUTH_RINTERR),
- username));
- }
-
- jlog.debug("Authentication done: " + username);
- return authentication;
- }
-
-}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth/ClientDeregistrationValidator.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/ClientDeregistrationValidator.java
similarity index 95%
rename from full/src/main/java/de/ids_mannheim/korap/oauth/ClientDeregistrationValidator.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/ClientDeregistrationValidator.java
index 60525b0..422b346 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth/ClientDeregistrationValidator.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/ClientDeregistrationValidator.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.oauth;
+package de.ids_mannheim.korap.oauth2;
import javax.servlet.http.HttpServletRequest;
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth/OAuthDeregisterClientRequest.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2DeregisterClientRequest.java
similarity index 83%
rename from full/src/main/java/de/ids_mannheim/korap/oauth/OAuthDeregisterClientRequest.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2DeregisterClientRequest.java
index 88ae668..ecc2bc0 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth/OAuthDeregisterClientRequest.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2DeregisterClientRequest.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.oauth;
+package de.ids_mannheim.korap.oauth2;
import javax.servlet.http.HttpServletRequest;
@@ -8,9 +8,9 @@
import org.apache.oltu.oauth2.common.utils.OAuthUtils;
import org.apache.oltu.oauth2.common.validators.OAuthValidator;
-public class OAuthDeregisterClientRequest extends OAuthRequest {
+public class OAuth2DeregisterClientRequest extends OAuthRequest {
- public OAuthDeregisterClientRequest (HttpServletRequest request)
+ public OAuth2DeregisterClientRequest (HttpServletRequest request)
throws OAuthSystemException, OAuthProblemException {
super(request);
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/constant/OAuth2ClientType.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2ClientType.java
similarity index 93%
rename from full/src/main/java/de/ids_mannheim/korap/constant/OAuth2ClientType.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2ClientType.java
index 3c4c57d..60209be 100644
--- a/full/src/main/java/de/ids_mannheim/korap/constant/OAuth2ClientType.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2ClientType.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.constant;
+package de.ids_mannheim.korap.oauth2.constant;
public enum OAuth2ClientType {
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Error.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Error.java
new file mode 100644
index 0000000..fbdb5a7
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Error.java
@@ -0,0 +1,103 @@
+package de.ids_mannheim.korap.oauth2.constant;
+
+public class OAuth2Error {
+
+ public static final String ERROR = "error";
+ public static final String DESCRIPTION = "error_description";
+ public static final String URI = "error_uri";
+
+
+ /**
+ * The request is missing a required parameter, includes an
+ * invalid parameter value, includes a parameter more than
+ * once, or is otherwise malformed.
+ */
+ public static final String INVALID_REQUEST = "invalid_request";
+
+ /**
+ * Client authentication failed (e.g., unknown client, no
+ * client authentication included, or unsupported
+ * authentication method). The authorization server MAY
+ * return an HTTP 401 (Unauthorized) status code to indicate
+ * which HTTP authentication schemes are supported. If the
+ * client attempted to authenticate via the "Authorization"
+ * request header field, the authorization server MUST
+ * respond with an HTTP 401 (Unauthorized) status code and
+ * include the "WWW-Authenticate" response header field
+ * matching the authentication scheme used by the client.
+ */
+ public static final String INVALID_CLIENT = "invalid_client";
+
+ /**
+ * The provided authorization grant (e.g., authorization
+ * code, resource owner credentials) or refresh token is
+ * invalid, expired, revoked, does not match the redirection
+ * URI used in the authorization request, or was issued to
+ * another client.
+ *
+ */
+ public static final String INVALID_GRANT = "invalid_grant";
+
+ /**
+ * The client is not authorized to request an authorization
+ * code using this method.
+ *
+ */
+ public static final String UNAUTHORIZED_CLIENT = "unauthorized_client";
+
+ /**
+ * The authorization grant type is not supported by the
+ * authorization server.
+ */
+ public static final String UNSUPPORTED_GRANT_TYPE =
+ "unsupported_grant_type";
+
+ /**
+ * The requested scope is invalid, unknown, or malformed.
+ *
+ */
+ public static final String INVALID_SCOPE = "invalid_scope";
+
+ /**
+ * The resource owner or authorization server denied the
+ * request.
+ *
+ */
+ public static final String ACCESS_DENIED = "access_denied";
+
+ /**
+ * The authorization server does not support obtaining an
+ * authorization code using this method.
+ */
+ public static final String UNSUPPORTED_RESPONSE_TYPE =
+ "unsupported_response_type";
+
+ /**
+ * The authorization server encountered an unexpected
+ * condition that prevented it from fulfilling the request.
+ * (This error code is needed because a 500 Internal Server
+ * Error HTTP status code cannot be returned to the client
+ * via an HTTP redirect.)
+ *
+ */
+ public static final String SERVER_ERROR = "server_error";
+
+ /**
+ * The authorization server is currently unable to handle
+ * the request due to a temporary overloading or maintenance
+ * of the server. (This error code is needed because a 503
+ * Service Unavailable HTTP status code cannot be returned
+ * to the client via an HTTP redirect.)
+ */
+ public static final String TEMPORARILY_UNAVAILABLE =
+ "temporarily_unavailable";
+
+
+ // extensions
+
+ public static final String INSUFFICIENT_SCOPE = "insufficient_scope";
+
+ public static final String INVALID_TOKEN = "invalid_token";
+
+ public static final String EXPIRED_TOKEN = "expired_token";
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessScopeDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessScopeDao.java
new file mode 100644
index 0000000..492a529
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessScopeDao.java
@@ -0,0 +1,33 @@
+package de.ids_mannheim.korap.oauth2.dao;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Root;
+
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+
+@Repository
+@Transactional
+public class AccessScopeDao {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ public List<AccessScope> retrieveAccessScopes () {
+ CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery<AccessScope> query =
+ builder.createQuery(AccessScope.class);
+ Root<AccessScope> root = query.from(AccessScope.class);
+ query.select(root);
+ Query q = entityManager.createQuery(query);
+ return q.getResultList();
+ }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
new file mode 100644
index 0000000..1e3866f
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
@@ -0,0 +1,25 @@
+package de.ids_mannheim.korap.oauth2.dao;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import de.ids_mannheim.korap.oauth2.entity.AccessToken;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
+
+@Repository
+@Transactional
+public class AccessTokenDao {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ public void storeAccessToken (Authorization authorization, String token) {
+ AccessToken accessToken = new AccessToken();
+ accessToken.setAuthorization(authorization);
+ accessToken.setToken(token);
+ entityManager.persist(accessToken);
+ }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java
new file mode 100644
index 0000000..0486420
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java
@@ -0,0 +1,66 @@
+package de.ids_mannheim.korap.oauth2.dao;
+
+import java.util.Set;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Expression;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.entity.Authorization_;
+import de.ids_mannheim.korap.utils.ParameterChecker;
+
+@Transactional
+@Repository
+public class AuthorizationDao {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ public void storeAuthorizationCode (String clientId, String userId,
+ String code, Set<AccessScope> scopes, String redirectURI) {
+ Authorization authCode = new Authorization();
+ authCode.setCode(code);
+ authCode.setClientId(clientId);
+ authCode.setUserId(userId);
+ authCode.setScopes(scopes);
+ authCode.setRedirectURI(redirectURI);
+
+ entityManager.persist(authCode);
+ // what if unique fails
+ }
+
+ public Authorization retrieveAuthorizationCode (String code,
+ String clientId) throws KustvaktException {
+ ParameterChecker.checkStringValue(code, "code");
+ ParameterChecker.checkStringValue(clientId, "client_id");
+
+ CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery<Authorization> query =
+ builder.createQuery(Authorization.class);
+ Root<Authorization> root = query.from(Authorization.class);
+
+ Predicate restrictions = builder.and(
+ builder.equal(root.get(Authorization_.code), code),
+ builder.equal(root.get(Authorization_.clientId), clientId));
+
+ query.select(root);
+ query.where(restrictions);
+ Query q = entityManager.createQuery(query);
+ return (Authorization) q.getSingleResult();
+ }
+
+ public void updateAuthorization (Authorization authorization) {
+ entityManager.merge(authorization);
+ }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
similarity index 93%
rename from full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
index e1759c4..1bad7fa 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.dao;
+package de.ids_mannheim.korap.oauth2.dao;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
@@ -11,11 +11,11 @@
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
-import de.ids_mannheim.korap.constant.OAuth2ClientType;
-import de.ids_mannheim.korap.entity.OAuth2Client;
-import de.ids_mannheim.korap.entity.OAuth2Client_;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client_;
import de.ids_mannheim.korap.utils.ParameterChecker;
@Transactional
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java
new file mode 100644
index 0000000..cc0e41a
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java
@@ -0,0 +1,44 @@
+package de.ids_mannheim.korap.oauth2.entity;
+
+import java.util.List;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "oauth2_access_scope")
+public class AccessScope {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private int id;
+ private String name;
+
+ @ManyToMany(mappedBy = "scopes", fetch = FetchType.LAZY)
+ private List<Authorization> authorizationCodes;
+
+ @Override
+ public String toString () {
+ return "id: " + id + ", name: " + name;
+ }
+
+ @Override
+ public boolean equals (Object obj) {
+ String scope = (String) obj;
+ if (scope.equals(this.name)) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java
new file mode 100644
index 0000000..5d36723
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java
@@ -0,0 +1,39 @@
+package de.ids_mannheim.korap.oauth2.entity;
+
+import java.time.ZonedDateTime;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "oauth2_access_token")
+public class AccessToken {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private int id;
+ private String token;
+ @Column(name = "created_date")
+ private ZonedDateTime createdDate;
+ @Column(name = "is_revoked")
+ private boolean isRevoked;
+ @Column(name = "total_attempts")
+ private int totalAttempts;
+
+ @OneToOne(fetch=FetchType.EAGER)
+ @JoinColumn(name="authorization_id")
+ private Authorization authorization;
+
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/Authorization.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/Authorization.java
new file mode 100644
index 0000000..4113f48
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/Authorization.java
@@ -0,0 +1,60 @@
+package de.ids_mannheim.korap.oauth2.entity;
+
+import java.time.ZonedDateTime;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "oauth2_authorization")
+public class Authorization {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private int id;
+ private String code;
+ @Column(name = "client_id")
+ private String clientId;
+ @Column(name = "user_id")
+ private String userId;
+ @Column(name = "redirect_uri")
+ private String redirectURI;
+ @Column(name = "created_date")
+ private ZonedDateTime createdDate;
+ @Column(name = "is_revoked")
+ private boolean isRevoked;
+ @Column(name = "total_attempts")
+ private int totalAttempts;
+
+ @ManyToMany(fetch = FetchType.EAGER)
+ @JoinTable(name = "oauth2_authorization_scope",
+ joinColumns = @JoinColumn(name = "authorization_id",
+ referencedColumnName = "id"),
+ inverseJoinColumns = @JoinColumn(name = "scope_id",
+ referencedColumnName = "id"),
+ uniqueConstraints = @UniqueConstraint(
+ columnNames = { "authorization_id", "scope_id" }))
+ private Set<AccessScope> scopes;
+
+ @Override
+ public String toString () {
+ return "code: " + code + ", " + "clientId: " + clientId + ", "
+ + "userId: " + userId + ", " + "createdDate: " + createdDate
+ + ", " + "isRevoked: " + isRevoked + ", " + "scopes: " + scopes
+ + ", " + "totalAttempts: " + totalAttempts;
+ }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
similarity index 91%
rename from full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
index 9efab11..17f0cb4 100644
--- a/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.entity;
+package de.ids_mannheim.korap.oauth2.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -7,7 +7,7 @@
import javax.persistence.Id;
import javax.persistence.Table;
-import de.ids_mannheim.korap.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
import lombok.Getter;
import lombok.Setter;
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AuthorizationService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AuthorizationService.java
new file mode 100644
index 0000000..bb014e5
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AuthorizationService.java
@@ -0,0 +1,220 @@
+package de.ids_mannheim.korap.oauth2.service;
+
+import java.time.ZonedDateTime;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
+import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
+import org.apache.oltu.oauth2.as.response.OAuthASResponse;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.message.OAuthResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.sun.jersey.api.client.ClientResponse.Status;
+
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.dao.AccessScopeDao;
+import de.ids_mannheim.korap.oauth2.dao.AuthorizationDao;
+import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+
+@Service
+public class OAuth2AuthorizationService {
+
+ private static Logger jlog =
+ LoggerFactory.getLogger(OAuth2AuthorizationService.class);
+
+ public static int MAX_ATTEMPTS = 3;
+
+ @Autowired
+ private OAuth2ClientService clientService;
+ @Autowired
+ private OAuth2Service auth2Service;
+ @Autowired
+ private OAuthIssuer oauthIssuer;
+
+ @Autowired
+ private AuthorizationDao authorizationDao;
+ @Autowired
+ private AccessScopeDao accessScopeDao;
+
+ public OAuthResponse requestAuthorizationCode (HttpServletRequest request,
+ OAuthAuthzRequest authzRequest, String authorization)
+ throws KustvaktException, OAuthSystemException {
+
+ String responseType = authzRequest.getResponseType();
+ if (responseType == null || responseType.isEmpty()) {
+ throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
+ "response_type is missing.", OAuth2Error.INVALID_REQUEST);
+ }
+
+ OAuth2Client client = clientService.authenticateClient(
+ authzRequest.getClientId(), authzRequest.getClientSecret());
+
+ String redirectUri = authzRequest.getRedirectURI();
+ boolean hasRedirectUri = hasRedirectUri(redirectUri);
+ redirectUri = verifyRedirectUri(client, hasRedirectUri, redirectUri);
+
+ String username = authzRequest.getParam(Attributes.USERNAME);
+ auth2Service.authenticateUser(username,
+ authzRequest.getParam(Attributes.PASSWORD),
+ authzRequest.getScopes());
+
+ String code = oauthIssuer.authorizationCode();
+ Set<AccessScope> scopes =
+ convertToAccessScope(authzRequest.getScopes());
+
+ authorizationDao.storeAuthorizationCode(authzRequest.getClientId(),
+ username, code, scopes, authzRequest.getRedirectURI());
+
+ return OAuthASResponse
+ .authorizationResponse(request, Status.FOUND.getStatusCode())
+ .setCode(code).location(redirectUri).buildQueryMessage();
+ }
+
+ private Set<AccessScope> convertToAccessScope (Set<String> scopes)
+ throws KustvaktException {
+
+ if (scopes.isEmpty()) {
+ // return default scopes
+ return null;
+ }
+
+ List<AccessScope> definedScopes = accessScopeDao.retrieveAccessScopes();
+ Set<AccessScope> requestedScopes =
+ new HashSet<AccessScope>(scopes.size());
+ int index;
+ for (String scope : scopes) {
+ index = definedScopes.indexOf(scope);
+ if (index == -1) {
+ throw new KustvaktException(StatusCodes.INVALID_SCOPE,
+ scope + " is invalid.", OAuth2Error.INVALID_SCOPE);
+ }
+ else {
+ requestedScopes.add(definedScopes.get(index));
+ }
+ }
+ return requestedScopes;
+ }
+
+ private boolean hasRedirectUri (String redirectURI) {
+ if (redirectURI != null && !redirectURI.isEmpty()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * If the request contains a redirect_uri parameter, the server
+ * must confirm it is a valid redirect URI.
+ *
+ * If there is no redirect_uri parameter in the request, and only
+ * one URI was registered, the server uses the redirect URL that
+ * was previously registered.
+ *
+ * If no redirect URL has been registered, this is an error.
+ *
+ * @param client
+ * an OAuth2Client
+ * @param hasRedirectUri
+ * true if request contains redirect_uri, false
+ * otherwise
+ * @param redirectUri
+ * the redirect_uri value
+ * @return a client's redirect URI
+ * @throws KustvaktException
+ */
+ private String verifyRedirectUri (OAuth2Client client,
+ boolean hasRedirectUri, String redirectUri)
+ throws KustvaktException {
+
+ String registeredUri = client.getRedirectURI();
+ if (hasRedirectUri) {
+ // check if the redirect URI the same as that in DB
+ if (!redirectUri.equals(registeredUri)) {
+ throw new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
+ redirectUri + " is unknown",
+ OAuth2Error.INVALID_REQUEST);
+ }
+ }
+ else {
+ // check if there is a redirect URI in the DB
+ // This should not happened as it is required in client registration!
+ if (registeredUri != null && !registeredUri.isEmpty()) {
+ redirectUri = registeredUri;
+ }
+ else {
+ throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
+ "redirect_uri is required",
+ OAuth2Error.INVALID_REQUEST);
+ }
+ }
+
+ return redirectUri;
+ }
+
+
+ public void verifyAuthorization (String code, String clientId,
+ String redirectURI) throws KustvaktException {
+ Authorization authorization =
+ authorizationDao.retrieveAuthorizationCode(code, clientId);
+
+ // EM: can Kustvakt be specific about the invalid request param?
+ if (authorization.isRevoked()) {
+ addTotalAttempts(authorization);
+ throw new KustvaktException(StatusCodes.INVALID_AUTHORIZATION,
+ "Invalid authorization", OAuth2Error.INVALID_REQUEST);
+ }
+
+ if (isExpired(authorization.getCreatedDate())) {
+ addTotalAttempts(authorization);
+ throw new KustvaktException(StatusCodes.INVALID_AUTHORIZATION,
+ "Authorization expired", OAuth2Error.INVALID_REQUEST);
+ }
+
+ String authorizedUri = authorization.getRedirectURI();
+ if (authorizedUri != null && !authorizedUri.isEmpty()
+ && !authorizedUri.equals(redirectURI)) {
+ addTotalAttempts(authorization);
+ throw new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
+ "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
+ }
+
+ authorization.setRevoked(true);
+ authorizationDao.updateAuthorization(authorization);
+ }
+
+ public void addTotalAttempts (Authorization authorization) {
+ int totalAttempts = authorization.getTotalAttempts() + 1;
+ if (totalAttempts > MAX_ATTEMPTS){
+ authorization.setRevoked(true);
+ }
+ else{
+ authorization.setTotalAttempts(totalAttempts);
+ }
+ authorizationDao.updateAuthorization(authorization);
+ }
+
+ private boolean isExpired (ZonedDateTime createdDate) {
+ jlog.debug("createdDate: " + createdDate);
+ ZonedDateTime expiration = createdDate.plusMinutes(10);
+ ZonedDateTime now = ZonedDateTime.now();
+ jlog.debug("expiration: " + expiration + ", now: " + now);
+
+ if (expiration.isAfter(now)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
similarity index 86%
rename from full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
index 1089c45..30d22a6 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.service;
+package de.ids_mannheim.korap.oauth2.service;
import java.net.MalformedURLException;
import java.net.URI;
@@ -9,26 +9,28 @@
import org.apache.commons.validator.routines.UrlValidator;
import org.apache.log4j.Logger;
import org.apache.oltu.oauth2.as.request.OAuthRequest;
-import org.apache.oltu.oauth2.common.error.OAuthError;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.ids_mannheim.korap.config.FullConfiguration;
-import de.ids_mannheim.korap.constant.OAuth2ClientType;
import de.ids_mannheim.korap.dao.AdminDao;
-import de.ids_mannheim.korap.dao.OAuth2ClientDao;
import de.ids_mannheim.korap.dto.OAuth2ClientDto;
-import de.ids_mannheim.korap.entity.OAuth2Client;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.interfaces.EncryptionIface;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
-/** According to RFC 6749, an authorization server MUST:
+/**
+ * According to RFC 6749, an authorization server MUST:
* <ul>
* <li>
* require client authentication for confidential clients or for any
- * client that was issued client credentials (or with other authentication
+ * client that was issued client credentials (or with other
+ * authentication
* requirements),
* </li>
*
@@ -62,13 +64,13 @@
if (!urlValidator.isValid(clientJson.getUrl())) {
throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
clientJson.getUrl() + " is invalid.",
- OAuthError.TokenResponse.INVALID_REQUEST);
+ OAuth2Error.INVALID_REQUEST);
}
if (!httpsValidator.isValid(clientJson.getRedirectURI())) {
throw new KustvaktException(StatusCodes.HTTPS_REQUIRED,
clientJson.getRedirectURI()
+ " is invalid. RedirectURI requires https.",
- OAuthError.TokenResponse.INVALID_REQUEST);
+ OAuth2Error.INVALID_REQUEST);
}
boolean isNative = isNativeClient(clientJson.getUrl(),
@@ -107,8 +109,7 @@
if (cause instanceof SQLException) {
throw new KustvaktException(
StatusCodes.CLIENT_REGISTRATION_FAILED,
- cause.getMessage(),
- OAuthError.TokenResponse.INVALID_REQUEST);
+ cause.getMessage(), OAuth2Error.INVALID_REQUEST);
}
lastCause = cause;
}
@@ -128,7 +129,7 @@
catch (MalformedURLException e) {
throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
"Invalid url :" + e.getMessage(),
- OAuthError.TokenResponse.INVALID_REQUEST);
+ OAuth2Error.INVALID_REQUEST);
}
String uriHost = null;
try {
@@ -137,7 +138,7 @@
catch (URISyntaxException e) {
throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
"Invalid redirectURI: " + e.getMessage(),
- OAuthError.TokenResponse.INVALID_REQUEST);
+ OAuth2Error.INVALID_REQUEST);
}
boolean isNative =
urlHost.equals(nativeHost) && uriHost.equals(nativeHost);
@@ -159,7 +160,7 @@
"Service is limited to public clients. To deregister "
+ "confidential clients, use service at path: "
+ "oauth2/client/deregister/confidential.",
- OAuthError.TokenResponse.INVALID_REQUEST);
+ OAuth2Error.INVALID_REQUEST);
}
else if (client.getRegisteredBy().equals(username)) {
clientDao.deregisterClient(client);
@@ -185,7 +186,8 @@
if (clientId == null || clientId.isEmpty()) {
throw new KustvaktException(
StatusCodes.CLIENT_AUTHENTICATION_FAILED,
- "Missing parameters: client id", "invalid_request");
+ "Missing parameters: client id",
+ OAuth2Error.INVALID_REQUEST);
}
OAuth2Client client = clientDao.retrieveClientById(clientId);
@@ -194,7 +196,8 @@
|| client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
throw new KustvaktException(
StatusCodes.CLIENT_AUTHENTICATION_FAILED,
- "Missing parameters: client_secret", "invalid_request");
+ "Missing parameters: client_secret",
+ OAuth2Error.INVALID_REQUEST);
}
else
return client;
@@ -209,8 +212,7 @@
}
throw new KustvaktException(StatusCodes.CLIENT_AUTHENTICATION_FAILED,
- "Invalid client credentials",
- OAuthError.TokenResponse.INVALID_CLIENT);
+ "Invalid client credentials", OAuth2Error.INVALID_CLIENT);
}
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2Service.java
similarity index 67%
rename from full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2Service.java
index 9e8092b..07037ce 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2Service.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.service;
+package de.ids_mannheim.korap.oauth2.service;
import java.util.HashMap;
import java.util.Map;
@@ -9,7 +9,6 @@
import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
import org.apache.oltu.oauth2.as.request.AbstractOAuthTokenRequest;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
-import org.apache.oltu.oauth2.common.error.OAuthError;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.apache.oltu.oauth2.common.message.types.GrantType;
@@ -19,10 +18,13 @@
import de.ids_mannheim.korap.config.Attributes;
import de.ids_mannheim.korap.config.FullConfiguration;
-import de.ids_mannheim.korap.entity.OAuth2Client;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
@Service
public class OAuth2Service {
@@ -30,38 +32,17 @@
@Autowired
private OAuth2ClientService clientService;
@Autowired
+ private OAuth2AuthorizationService authorizationService;
+ @Autowired
+ private AccessTokenDao tokenDao;
+
+ @Autowired
private FullConfiguration config;
@Autowired
private AuthenticationManagerIface authenticationManager;
@Autowired
private OAuthIssuer oauthIssuer;
- /**
- * OAuth2 describes various ways for requesting an access token. Kustvakt
- * supports:
- * <ul>
- * <li> Authorization code grant: obtains authorization from a third party
- * application.
- * </li>
- * <li> Resource owner password grant: strictly for clients that are parts
- * of KorAP. Clients use user credentials, e.g. Kalamar (front-end) with
- * login form.
- * </li>
- * <li> Client credentials grant: strictly for clients that are parts
- * of KorAP. Clients access their own resources, not on behalf of a
- * user.
- * </li>
- * </ul>
- *
- *
- * @param request
- *
- * @param oAuthRequest
- * @param authorization
- * @return
- * @throws KustvaktException
- * @throws OAuthSystemException
- */
public OAuthResponse requestAccessToken (
AbstractOAuthTokenRequest oAuthRequest)
throws KustvaktException, OAuthSystemException {
@@ -86,21 +67,25 @@
else {
throw new KustvaktException(StatusCodes.UNSUPPORTED_GRANT_TYPE,
grantType + " is not supported.",
- OAuthError.TokenResponse.UNSUPPORTED_GRANT_TYPE);
+ OAuth2Error.UNSUPPORTED_GRANT_TYPE);
}
}
/**
- * RFC 6749:
- * If the client type is confidential or the client was issued client
- * credentials, the client MUST authenticate with the authorization server.
+ * RFC 6749:
+ * If the client type is confidential or the client was issued
+ * client credentials, the client MUST authenticate with the
+ * authorization server.
*
* @param authorizationCode
- * @param redirectURI required if included in the authorization request
- * @param clientId required if there is no authorization header
- * @param clientSecret clilent_secret, required if client_secret was issued
- * for the client in client registration.
+ * @param redirectURI
+ * required if included in the authorization request
+ * @param clientId
+ * required if there is no authorization header
+ * @param clientSecret
+ * clilent_secret, required if client_secret was issued
+ * for the client in client registration.
* @return
* @throws OAuthSystemException
* @throws KustvaktException
@@ -111,33 +96,37 @@
throws KustvaktException, OAuthSystemException {
clientService.authenticateClient(clientId, clientSecret);
+ authorizationService.verifyAuthorization(authorizationCode, clientId,
+ redirectURI);
- // TODO
- // check authorization code
- // check redirectURI
return createsAccessTokenResponse();
}
-
- /** Third party apps must not be allowed to use password grant.
- * MH: password grant is only allowed for trusted clients (korap frontend)
- *
- * According to RFC 6749, client authentication is only required for
- * confidential clients and whenever client credentials are provided.
- * Moreover, client_id is optional for password grant, but without it,
- * the authentication server cannot check the client type.
+ /**
+ * Third party apps must not be allowed to use password grant.
+ * MH: password grant is only allowed for trusted clients (korap
+ * frontend)
*
- * To make sure that confidential clients authenticate, client_id is made
- * required (similar to authorization code grant).
+ * According to RFC 6749, client authentication is only required
+ * for confidential clients and whenever client credentials are
+ * provided. Moreover, client_id is optional for password grant,
+ * but without it, the authentication server cannot check the
+ * client type. To make sure that confidential clients
+ * authenticate, client_id is made required (similar to
+ * authorization code grant).
*
*
- * @param username username, required
- * @param password user password, required
+ * @param username
+ * username, required
+ * @param password
+ * user password, required
* @param scopes
- * @param clientId client_id, required
- * @param clientSecret clilent_secret, required if client_secret was issued
- * for the client in client registration.
+ * @param clientId
+ * client_id, required
+ * @param clientSecret
+ * clilent_secret, required if client_secret was issued
+ * for the client in client registration.
* @return
* @throws KustvaktException
* @throws OAuthSystemException
@@ -152,7 +141,7 @@
if (!client.isNative()) {
throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
"Password grant is not allowed for third party clients",
- OAuthError.TokenResponse.UNAUTHORIZED_CLIENT);
+ OAuth2Error.UNAUTHORIZED_CLIENT);
}
authenticateUser(username, password, scopes);
@@ -163,13 +152,11 @@
Set<String> scopes) throws KustvaktException {
if (username == null || username.isEmpty()) {
throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
- "username is missing.",
- OAuthError.TokenResponse.INVALID_REQUEST);
+ "username is missing.", OAuth2Error.INVALID_REQUEST);
}
if (password == null || password.isEmpty()) {
throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
- "password is missing",
- OAuthError.TokenResponse.INVALID_REQUEST);
+ "password is missing", OAuth2Error.INVALID_REQUEST);
}
Map<String, Object> attributes = new HashMap<>();
@@ -181,10 +168,13 @@
attributes);
}
- /** Clients must authenticate
+ /**
+ * Clients must authenticate.
*
- * @param clientId client_id parameter, required
- * @param clientSecret client_secret parameter, required
+ * @param clientId
+ * client_id parameter, required
+ * @param clientSecret
+ * client_secret parameter, required
* @param scopes
* @return
* @throws KustvaktException
@@ -197,7 +187,8 @@
if (clientSecret == null || clientSecret.isEmpty()) {
throw new KustvaktException(
StatusCodes.CLIENT_AUTHENTICATION_FAILED,
- "Missing parameters: client_secret", "invalid_request");
+ "Missing parameters: client_secret",
+ OAuth2Error.INVALID_REQUEST);
}
clientService.authenticateClient(clientId, clientSecret);
@@ -205,18 +196,26 @@
}
-
- /** Creates an OAuthResponse containing an access token and a refresh token
- * with type Bearer.
+ /**
+ * Creates an OAuthResponse containing an access token and a
+ * refresh token with type Bearer.
*
* @return an OAuthResponse containing an access token
* @throws OAuthSystemException
*/
+
private OAuthResponse createsAccessTokenResponse ()
throws OAuthSystemException {
+ return createsAccessTokenResponse(null);
+ }
+
+ private OAuthResponse createsAccessTokenResponse (
+ Authorization authorization) throws OAuthSystemException {
String accessToken = oauthIssuer.accessToken();
String refreshToken = oauthIssuer.refreshToken();
+ tokenDao.storeAccessToken(authorization, accessToken);
+
OAuthResponse r =
OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
.setAccessToken(accessToken)
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2AuthorizationService.java b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2AuthorizationService.java
deleted file mode 100644
index 751c7d2..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2AuthorizationService.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package de.ids_mannheim.korap.service;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
-import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
-import org.apache.oltu.oauth2.as.response.OAuthASResponse;
-import org.apache.oltu.oauth2.common.error.OAuthError;
-import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
-import org.apache.oltu.oauth2.common.message.OAuthResponse;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import com.sun.jersey.api.client.ClientResponse.Status;
-
-import de.ids_mannheim.korap.config.Attributes;
-import de.ids_mannheim.korap.entity.OAuth2Client;
-import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.exceptions.StatusCodes;
-
-@Service
-public class OAuth2AuthorizationService {
-
- @Autowired
- private OAuth2ClientService clientService;
- @Autowired
- private OAuth2Service auth2Service;
- @Autowired
- private OAuthIssuer oauthIssuer;
-
- public OAuthResponse requestAuthorizationCode (HttpServletRequest request,
- OAuthAuthzRequest authzRequest, String authorization)
- throws KustvaktException, OAuthSystemException {
-
- String responseType = authzRequest.getResponseType();
- if (responseType == null || responseType.isEmpty()) {
- throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
- "response_type is missing.",
- OAuthError.CodeResponse.INVALID_REQUEST);
- }
-
- OAuth2Client client;
- try {
- client = clientService.authenticateClient(
- authzRequest.getClientId(), authzRequest.getClientSecret());
- }
- catch (KustvaktException e) {
- e.setEntity(OAuthError.CodeResponse.UNAUTHORIZED_CLIENT);
- throw e;
- }
-
- String redirectUri = authzRequest.getRedirectURI();
- boolean hasRedirectUri = hasRedirectUri(redirectUri);
- redirectUri = verifyRedirectUri(client, hasRedirectUri, redirectUri);
-
- auth2Service.authenticateUser(
- authzRequest.getParam(Attributes.USERNAME),
- authzRequest.getParam(Attributes.PASSWORD),
- authzRequest.getScopes());
-
- return OAuthASResponse
- .authorizationResponse(request, Status.FOUND.getStatusCode())
- .setCode(oauthIssuer.authorizationCode()).location(redirectUri)
- .buildQueryMessage();
- }
-
-
- private boolean hasRedirectUri (String redirectURI) {
- if (redirectURI != null && !redirectURI.isEmpty()) {
- return true;
- }
- return false;
- }
-
- /** If the request contains a redirect_uri parameter, the server must confirm
- * it is a valid redirect URI.
- *
- * If there is no redirect_uri parameter in the request, and only one URI
- * was registered, the server uses the redirect URL that was previously
- * registered.
- *
- * If no redirect URL has been registered, this is an error.
- *
- * @param client an OAuth2Client
- * @param hasRedirectUri true if request contains redirect_uri, false otherwise
- * @param redirectUri the redirect_uri value
- * @return a client's redirect URI
- * @throws KustvaktException
- */
- private String verifyRedirectUri (OAuth2Client client,
- boolean hasRedirectUri, String redirectUri)
- throws KustvaktException {
-
- String registeredUri = client.getRedirectURI();
- if (hasRedirectUri) {
- // check if the redirect URI the same as that in DB
- if (!redirectUri.equals(registeredUri)) {
- throw new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
- redirectUri + " is unknown",
- OAuthError.CodeResponse.INVALID_REQUEST);
- }
- }
- else {
- // check if there is a redirect URI in the DB
- // This should not happened as it is required in client registration!
- if (registeredUri != null && !registeredUri.isEmpty()) {
- redirectUri = registeredUri;
- }
- else {
- throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
- "redirect_uri is required",
- OAuthError.CodeResponse.INVALID_REQUEST);
- }
- }
-
- return redirectUri;
- }
-}
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java b/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
index 0d31236..f134c79 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
@@ -17,52 +17,19 @@
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.interfaces.db.AuditingIface;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
-/** OAuth2ResponseHandler builds {@link Response}s from
- * {@link OAuthResponse}s and handles exceptions by building
- * OAuth error responses accordingly.
+/**
+ * OAuth2ResponseHandler builds {@link Response}s from
+ * {@link OAuthResponse}s and handles exceptions by building
+ * OAuth error responses accordingly.
*
* <br/><br/>
*
- * OAuth2 error response consists of error (required),
+ * OAuth2 error response consists of error (required),
* error_description (optional) and error_uri (optional).
*
- * According to RFC 6749, error indicates error code
- * categorized into:
- * <ul>
- * <li>invalid_request: The request is missing a required parameter,
- * includes an unsupported parameter value (other than grant type),
- * repeats a parameter, includes multiple credentials, utilizes
- * more than one mechanism for authenticating the client, or is
- * otherwise malformed.</li>
- *
- * <li>invalid_client: Client authentication failed (e.g., unknown
- * client, no client authentication included, or unsupported
- * authentication method). The authorization sever MAY return
- * an HTTP 401 (Unauthorized) status code to indicate which
- * HTTP authentication schemes are supported. If the client
- * attempted to authenticate via the "Authorization" request
- * header field, the authorization server MUST respond with
- * an HTTP 401 (Unauthorized) status code and include
- * the "WWW-Authenticate" response header field matching
- * the authentication scheme used by the client</li>
- *
- * <li>invalid_grant: The provided authorization grant
- * (e.g., authorization code, resource owner credentials) or
- * refresh token is invalid, expired, revoked, does not match
- * the redirection URI used in the authorization request, or
- * was issued to another client.</li>
- *
- * <li>unauthorized_client:The authenticated client is not
- * authorized to use this authorization grant type.</li>
- *
- * <li>unsupported_grant_type: The authorization grant type
- * is not supported by the authorization server.</li>
- *
- * <li>invalid_scope: The requested scope is invalid, unknown,
- * malformed, or exceeds the scope granted by the resource owner.</li>
- * </ul>
- *
+ * @see OAuth2Error
*
* @author margaretha
*
@@ -95,21 +62,32 @@
OAuthResponse oAuthResponse = null;
String errorCode = e.getEntity();
try {
- if (errorCode.equals(OAuthError.TokenResponse.INVALID_CLIENT)
- || errorCode.equals(
- OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)) {
+ if (errorCode.equals(OAuth2Error.INVALID_CLIENT)
+ || errorCode.equals(OAuth2Error.UNAUTHORIZED_CLIENT)
+ || errorCode.equals(OAuth2Error.INVALID_TOKEN)) {
oAuthResponse = createOAuthResponse(e,
Status.UNAUTHORIZED.getStatusCode());
}
- else if (errorCode.equals(OAuthError.TokenResponse.INVALID_GRANT)
- || errorCode
- .equals(OAuthError.TokenResponse.INVALID_REQUEST)
- || errorCode.equals(OAuthError.TokenResponse.INVALID_SCOPE)
- || errorCode.equals(
- OAuthError.TokenResponse.UNSUPPORTED_GRANT_TYPE)) {
+ else if (errorCode.equals(OAuth2Error.INVALID_GRANT)
+ || errorCode.equals(OAuth2Error.INVALID_REQUEST)
+ || errorCode.equals(OAuth2Error.INVALID_SCOPE)
+ || errorCode.equals(OAuth2Error.UNSUPPORTED_GRANT_TYPE)
+ || errorCode.equals(OAuth2Error.UNSUPPORTED_RESPONSE_TYPE)
+ || errorCode.equals(OAuth2Error.ACCESS_DENIED)) {
oAuthResponse = createOAuthResponse(e,
Status.BAD_REQUEST.getStatusCode());
-
+ }
+ else if (errorCode.equals(OAuth2Error.INSUFFICIENT_SCOPE)) {
+ oAuthResponse = createOAuthResponse(e,
+ Status.FORBIDDEN.getStatusCode());
+ }
+ else if (errorCode.equals(OAuth2Error.SERVER_ERROR)) {
+ oAuthResponse = createOAuthResponse(e,
+ Status.INTERNAL_SERVER_ERROR.getStatusCode());
+ }
+ else if (errorCode.equals(OAuth2Error.TEMPORARILY_UNAVAILABLE)) {
+ oAuthResponse = createOAuthResponse(e,
+ Status.SERVICE_UNAVAILABLE.getStatusCode());
}
else {
return super.throwit(e);
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
index b0699b1..ac219bc 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
@@ -24,8 +24,8 @@
import org.springframework.stereotype.Controller;
import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.service.OAuth2AuthorizationService;
-import de.ids_mannheim.korap.service.OAuth2Service;
+import de.ids_mannheim.korap.oauth2.service.OAuth2AuthorizationService;
+import de.ids_mannheim.korap.oauth2.service.OAuth2Service;
import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
import de.ids_mannheim.korap.web.utils.FormRequestWrapper;
@@ -40,15 +40,26 @@
@Autowired
private OAuth2AuthorizationService authorizationService;
- /** Kustvakt supports authorization only with Kalamar as the authorization
- * web-frontend or user interface. Thus authorization code request requires
- * user credentials in the request body, similar to access token request in
- * resource owner password grant request.
+ /**
+ * Requests an authorization code.
*
- * @param request
- * @param authorization
- * @param form
- * @return
+ * Kustvakt supports authorization only with Kalamar as the
+ * authorization web-frontend or user interface. Thus
+ * authorization code request requires user credentials in the
+ * request body, similar to access token request in
+ * resource owner password grant request.
+ *
+ * <br /><br />
+ * RFC 6749:
+ * If the client omits the scope parameter when requesting
+ * authorization, the authorization server MUST either process the
+ * request using a pre-defined default value or fail the request
+ * indicating an invalid scope.
+ *
+ * @param request HttpServletRequest
+ * @param authorization authorization header
+ * @param form form parameters
+ * @return a redirect URL
*/
@POST
@Path("authorize")
@@ -81,16 +92,50 @@
}
- /** Grants a client an access token, namely a string used in authenticated
- * requests representing user authorization for the client to access user
- * resources.
+ /**
+ * Grants a client an access token, namely a string used in
+ * authenticated requests representing user authorization for
+ * the client to access user resources.
*
- * @param request the request
- * @param authorization authorization header
- * @param form form parameters in a map
- * @return a JSON object containing an access token, a refresh token,
- * a token type and token expiry/life time (in seconds) if successful,
- * an error code and an error description otherwise.
+ * <br /><br />
+ *
+ * OAuth2 describes various ways of requesting an access token.
+ * Kustvakt supports:
+ * <ul>
+ * <li> Authorization code grant: obtains authorization from a
+ * third party application. Required parameters: grant_type,
+ * code, client_id, redirect_uri (if specified in the
+ * authorization request), client_secret (if the client is
+ * confidential or issued a secret).
+ * </li>
+ * <li> Resource owner password grant: strictly for clients that
+ * are parts of KorAP. Clients use user credentials, e.g. Kalamar
+ * (front-end) with login form. Required parameters: grant_type,
+ * username, password, client_id, client_secret (if the client is
+ * confidential or issued a secret). Optional parameters: scope.
+ * </li>
+ * <li> Client credentials grant: strictly for clients that are
+ * parts of KorAP. Clients access their own resources, not on
+ * behalf of a user. Required parameters: grant_type, client_id,
+ * client_secret. Optional parameters: scope.
+ * </li>
+ * </ul>
+ *
+ * <br /><br />
+ * RFC 6749: The value of the scope parameter is expressed as a
+ * list of space-delimited, case-sensitive strings defined by the
+ * authorization server.
+ *
+ * @param request
+ * the request
+ * @param authorization
+ * authorization header
+ * @param form
+ * form parameters in a map
+ * @return a JSON object containing an access token, a refresh
+ * token, a token type and the token expiration in seconds
+ * if successful, an error code and an error description
+ * otherwise.
*/
@POST
@Path("token")
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
index 39b93b7..05c0bd0 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
@@ -23,9 +23,9 @@
import de.ids_mannheim.korap.dto.OAuth2ClientDto;
import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.oauth.OAuthDeregisterClientRequest;
+import de.ids_mannheim.korap.oauth2.OAuth2DeregisterClientRequest;
+import de.ids_mannheim.korap.oauth2.service.OAuth2ClientService;
import de.ids_mannheim.korap.security.context.TokenContext;
-import de.ids_mannheim.korap.service.OAuth2ClientService;
import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
import de.ids_mannheim.korap.web.filter.BlockingFilter;
@@ -122,7 +122,7 @@
@Context HttpServletRequest request,
MultivaluedMap<String, String> form) {
try {
- OAuthRequest oAuthRequest = new OAuthDeregisterClientRequest(
+ OAuthRequest oAuthRequest = new OAuth2DeregisterClientRequest(
new FormRequestWrapper(request, form));
clientService.deregisterConfidentialClient(oAuthRequest);
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java b/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
index 99705e8..cec1806 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
@@ -1,6 +1,6 @@
package de.ids_mannheim.korap.web.input;
-import de.ids_mannheim.korap.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
import lombok.Getter;
import lombok.Setter;
diff --git a/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql b/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
index c055f4d..28a5de2 100644
--- a/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
+++ b/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
@@ -16,3 +16,55 @@
CREATE UNIQUE INDEX client_id_index on oauth2_client(id);
CREATE UNIQUE INDEX client_url_index on oauth2_client(url_hashcode);
+
+CREATE TABLE IF NOT EXISTS oauth2_authorization (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ code VARCHAR(250) NOT NULL,
+ client_id VARCHAR(100) NOT NULL,
+ user_id VARCHAR(100) NOT NULL,
+ redirect_uri TEXT DEFAULT NULL,
+ created_date timestamp DEFAULT (datetime('now','localtime')),
+ is_revoked BOOLEAN DEFAULT 0,
+ total_attempts INTEGER DEFAULT 0,
+ FOREIGN KEY (client_id)
+ REFERENCES oauth2_client(id)
+);
+
+CREATE UNIQUE INDEX authorization_index on oauth2_authorization(code, client_id);
+
+CREATE TABLE IF NOT EXISTS oauth2_access_scope (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name VARCHAR(200) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS oauth2_authorization_scope (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ authorization_id INTEGER NOT NULL,
+ scope_id INTEGER NOT NULL,
+ FOREIGN KEY (authorization_id)
+ REFERENCES oauth2_authorization(id),
+ FOREIGN KEY (scope_id)
+ REFERENCES access_scope(id)
+);
+
+CREATE UNIQUE INDEX authorization_scope_index on
+ oauth2_authorization_scope(authorization_id, scope_id);
+
+CREATE TRIGGER insert_created_date AFTER INSERT ON oauth2_authorization
+ BEGIN
+ UPDATE oauth2_authorization
+ SET created_date = DATETIME('now', 'localtime')
+ WHERE rowid = new.rowid;
+ END;
+
+CREATE TABLE IF NOT EXISTS oauth2_access_token (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ token VARCHAR(300) NOT NULL,
+ authorization_id INTEGER DEFAULT NULL,
+ created_date timestamp DEFAULT (datetime('now','localtime')),
+ is_revoked BOOLEAN DEFAULT 0,
+ total_attempts INTEGER DEFAULT 0,
+ FOREIGN KEY (authorization_id)
+ REFERENCES oauth2_authorization(id)
+);
+
diff --git a/full/src/main/resources/default-config.xml b/full/src/main/resources/default-config.xml
index 57f65ae..955d197 100644
--- a/full/src/main/resources/default-config.xml
+++ b/full/src/main/resources/default-config.xml
@@ -122,7 +122,12 @@
<!-- <property name="dataSource" ref="dataSource" /> -->
<property name="dataSource" ref="sqliteDataSource" />
- <property name="packagesToScan" value="de.ids_mannheim.korap.entity" />
+ <property name="packagesToScan">
+ <array>
+ <value>de.ids_mannheim.korap.entity</value>
+ <value>de.ids_mannheim.korap.oauth2.entity</value>
+ </array>
+ </property>
<property name="jpaVendorAdapter">
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
@@ -170,7 +175,7 @@
<constructor-arg value="http,https" />
</bean>
<bean id="httpsValidator" class="org.apache.commons.validator.routines.UrlValidator">
- <constructor-arg value="https"/>
+ <constructor-arg value="https" />
</bean>
<bean id="kustvakt_rewrite" class="de.ids_mannheim.korap.rewrite.FullRewriteHandler">
@@ -184,12 +189,12 @@
<bean id="kustvaktResponseHandler" class="de.ids_mannheim.korap.web.KustvaktExceptionHandler">
<constructor-arg index="0" name="iface" ref="kustvakt_auditing" />
</bean>
-
+
<!-- OAuth -->
<bean id="oauth2ResponseHandler" class="de.ids_mannheim.korap.web.OAuth2ResponseHandler">
<constructor-arg index="0" name="iface" ref="kustvakt_auditing" />
</bean>
-
+
<bean id="mdGenerator" class="org.apache.oltu.oauth2.as.issuer.MD5Generator">
</bean>
<bean id="oauthIssuer" class="org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl">
@@ -208,8 +213,7 @@
<constructor-arg ref="kustvakt_db" />
</bean>
- <bean name="kustvakt_encryption"
- class="de.ids_mannheim.korap.encryption.KustvaktEncryption">
+ <bean name="kustvakt_encryption" class="de.ids_mannheim.korap.encryption.KustvaktEncryption">
<constructor-arg ref="kustvakt_config" />
</bean>
@@ -218,9 +222,6 @@
<constructor-arg type="de.ids_mannheim.korap.config.KustvaktConfiguration"
ref="kustvakt_config" /> </bean> -->
- <bean id="client_auth"
- class="de.ids_mannheim.korap.authentication.OAuth2ClientAuthentication" />
-
<bean id="ldap_auth" class="de.ids_mannheim.korap.authentication.LdapAuth3">
<constructor-arg type="de.ids_mannheim.korap.config.KustvaktConfiguration"
ref="kustvakt_config" />
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
index b194e08..c32ccac 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
@@ -11,7 +11,6 @@
import javax.ws.rs.core.MultivaluedMap;
import org.apache.http.entity.ContentType;
-import org.apache.oltu.oauth2.common.error.OAuthError;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,8 +26,9 @@
import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
import de.ids_mannheim.korap.config.Attributes;
import de.ids_mannheim.korap.config.SpringJerseyTest;
-import de.ids_mannheim.korap.constant.OAuth2ClientType;
import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
import de.ids_mannheim.korap.utils.JsonUtils;
import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
@@ -87,7 +87,7 @@
response = testRegisterConfidentialClient();
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
node = JsonUtils.readTree(response.getEntity(String.class));
- assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
+ assertEquals(OAuth2Error.INVALID_REQUEST,
node.at("/error").asText());
testDeregisterConfidentialClientMissingParameters();
@@ -199,7 +199,7 @@
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
- assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
+ assertEquals(OAuth2Error.INVALID_REQUEST,
node.at("/error").asText());
assertEquals("Missing parameters: client_secret client_id",
node.at("/error_description").asText());
@@ -223,7 +223,7 @@
assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
- assertEquals(OAuthError.TokenResponse.INVALID_CLIENT,
+ assertEquals(OAuth2Error.INVALID_CLIENT,
node.at("/error").asText());
assertEquals("Invalid client credentials",
node.at("/error_description").asText());
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
index 3214fda..835284b 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
@@ -11,6 +11,7 @@
import org.apache.http.entity.ContentType;
import org.apache.oltu.oauth2.common.error.OAuthError;
+import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.apache.oltu.oauth2.common.message.types.TokenType;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -55,6 +56,7 @@
form.add("client_id", "fCBbQkAyYzI4NzUxMg");
form.add("username", "dory");
form.add("password", "password");
+
ClientResponse response = requestAuthorizationConfidentialClient(form);
assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
@@ -136,6 +138,36 @@
}
@Test
+ public void testRequestTokenAuthorizationConfidential ()
+ throws KustvaktException {
+
+ MultivaluedMap<String, String> authForm = new MultivaluedMapImpl();
+ authForm.add("response_type", "code");
+ authForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+ authForm.add("username", "dory");
+ authForm.add("password", "password");
+// form.add("scope", "read");
+ ClientResponse response = requestAuthorizationConfidentialClient(authForm);
+ URI redirectUri = response.getLocation();
+ String code = redirectUri.getQuery().split("=")[1];
+
+ MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
+ tokenForm.add("grant_type", "authorization_code");
+ tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+ tokenForm.add("client_secret", "secret");
+ tokenForm.add("code", code);
+
+ response = requestToken(tokenForm);
+ String entity = response.getEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node.at("/access_token").asText());
+ assertNotNull(node.at("/refresh_token").asText());
+ assertEquals(TokenType.BEARER.toString(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ }
+
+ @Test
public void testRequestTokenPasswordGrantConfidential ()
throws KustvaktException {
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
diff --git a/full/src/test/resources/test-config.xml b/full/src/test/resources/test-config.xml
index c0f7a2e..8f0d54f 100644
--- a/full/src/test/resources/test-config.xml
+++ b/full/src/test/resources/test-config.xml
@@ -123,7 +123,12 @@
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- <property name="dataSource" ref="dataSource" /> -->
<property name="dataSource" ref="sqliteDataSource" />
- <property name="packagesToScan" value="de.ids_mannheim.korap.entity" />
+ <property name="packagesToScan">
+ <array>
+ <value>de.ids_mannheim.korap.entity</value>
+ <value>de.ids_mannheim.korap.oauth2.entity</value>
+ </array>
+ </property>
<property name="jpaVendorAdapter">
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">