blob: 6d61e0207543ab341a3ca8edbf9b89fa78579832 [file] [log] [blame]
margarethaa452c5e2018-04-25 22:48:09 +02001package de.ids_mannheim.korap.oauth2.service;
margaretha31a9f522018-04-03 20:40:45 +02002
margaretha6374f722018-04-17 18:45:57 +02003import java.net.MalformedURLException;
4import java.net.URI;
5import java.net.URISyntaxException;
6import java.net.URL;
margaretha8d804f62018-04-10 12:39:56 +02007import java.sql.SQLException;
8
margaretha31a9f522018-04-03 20:40:45 +02009import org.apache.commons.validator.routines.UrlValidator;
10import org.springframework.beans.factory.annotation.Autowired;
11import org.springframework.stereotype.Service;
12
margaretha6d61a552018-04-10 19:26:44 +020013import de.ids_mannheim.korap.config.FullConfiguration;
margaretha8d804f62018-04-10 12:39:56 +020014import de.ids_mannheim.korap.dao.AdminDao;
margaretha0e8f4e72018-04-05 14:11:52 +020015import de.ids_mannheim.korap.dto.OAuth2ClientDto;
margaretha31a9f522018-04-03 20:40:45 +020016import de.ids_mannheim.korap.exceptions.KustvaktException;
17import de.ids_mannheim.korap.exceptions.StatusCodes;
18import de.ids_mannheim.korap.interfaces.EncryptionIface;
margarethaa452c5e2018-04-25 22:48:09 +020019import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
20import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
21import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
22import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
margaretha31a9f522018-04-03 20:40:45 +020023import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
24
margarethaa452c5e2018-04-25 22:48:09 +020025/**
26 * According to RFC 6749, an authorization server MUST:
margarethaa0486272018-04-12 19:59:31 +020027 * <ul>
28 * <li>
29 * require client authentication for confidential clients or for any
margarethaa452c5e2018-04-25 22:48:09 +020030 * client that was issued client credentials (or with other
31 * authentication
margarethaa0486272018-04-12 19:59:31 +020032 * requirements),
33 * </li>
34 *
35 * <li>authenticate the client if client authentication is included
36 * </li>
37 * </ul>
38 *
39 * @author margaretha
40 *
41 */
margaretha31a9f522018-04-03 20:40:45 +020042@Service
43public class OAuth2ClientService {
44
45 @Autowired
46 private OAuth2ClientDao clientDao;
47 @Autowired
margaretha8d804f62018-04-10 12:39:56 +020048 private AdminDao adminDao;
49 @Autowired
margarethae4034a82018-07-02 14:46:59 +020050 private UrlValidator redirectURIValidator;
margarethaa0486272018-04-12 19:59:31 +020051 @Autowired
margarethad7cab212018-07-02 19:01:43 +020052 private UrlValidator urlValidator;
53 @Autowired
margaretha31a9f522018-04-03 20:40:45 +020054 private EncryptionIface encryption;
margaretha0e8f4e72018-04-05 14:11:52 +020055 @Autowired
margaretha6d61a552018-04-10 19:26:44 +020056 private FullConfiguration config;
margaretha31a9f522018-04-03 20:40:45 +020057
margaretha0e8f4e72018-04-05 14:11:52 +020058 public OAuth2ClientDto registerClient (OAuth2ClientJson clientJson,
59 String registeredBy) throws KustvaktException {
margarethad7cab212018-07-02 19:01:43 +020060 String url = clientJson.getUrl();
61 int urlHashCode = 0;
62 if (url != null && !url.isEmpty()) {
63 urlHashCode = clientJson.getUrl().hashCode();
64 if (!redirectURIValidator.isValid(url)) {
65 throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
66 url + " is invalid.", OAuth2Error.INVALID_REQUEST);
67 }
margaretha31a9f522018-04-03 20:40:45 +020068 }
margarethad7cab212018-07-02 19:01:43 +020069
70 String redirectURI = clientJson.getRedirectURI();
71 if (redirectURI != null && !redirectURI.isEmpty()
72 && !urlValidator.isValid(redirectURI)) {
73 throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
74 redirectURI + " is invalid.", OAuth2Error.INVALID_REQUEST);
75 }
76
77 boolean isNative = isNativeClient(url, redirectURI);
margaretha6374f722018-04-17 18:45:57 +020078
margaretha31a9f522018-04-03 20:40:45 +020079 String secret = null;
margaretha6d61a552018-04-10 19:26:44 +020080 String secretHashcode = null;
margaretha0e8f4e72018-04-05 14:11:52 +020081 if (clientJson.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
margarethafb1e0992018-04-10 14:58:28 +020082 // RFC 6749:
margaretha07402f42018-05-07 19:07:45 +020083 // The authorization server MUST NOT issue client
84 // passwords or other client credentials to native
85 // application (clients installed and executed on the
86 // device used by the resource owner e.g. desktop
87 // application, native mobile application) or
88 // user-agent-based application clients for client
89 // authentication. The authorization server MAY issue a
90 // client password or other credentials for a specific
91 // installation of a native application client on a
margarethafb1e0992018-04-10 14:58:28 +020092 // specific device.
93
margaretha31a9f522018-04-03 20:40:45 +020094 secret = encryption.createToken();
margaretha6d61a552018-04-10 19:26:44 +020095 secretHashcode = encryption.secureHash(secret,
96 config.getPasscodeSaltField());
margaretha31a9f522018-04-03 20:40:45 +020097 }
98
99 String id = encryption.createRandomNumber();
margaretha8d804f62018-04-10 12:39:56 +0200100 try {
margaretha6d61a552018-04-10 19:26:44 +0200101 clientDao.registerClient(id, secretHashcode, clientJson.getName(),
margarethad7cab212018-07-02 19:01:43 +0200102 clientJson.getType(), isNative, url, urlHashCode,
103 redirectURI, registeredBy, clientJson.getDescription());
margaretha8d804f62018-04-10 12:39:56 +0200104 }
105 catch (Exception e) {
106 Throwable cause = e;
107 Throwable lastCause = null;
108 while ((cause = cause.getCause()) != null
109 && !cause.equals(lastCause)) {
110 if (cause instanceof SQLException) {
margarethab1081b12018-07-03 23:35:01 +0200111 break;
margaretha8d804f62018-04-10 12:39:56 +0200112 }
113 lastCause = cause;
114 }
margarethab1081b12018-07-03 23:35:01 +0200115 throw new KustvaktException(StatusCodes.CLIENT_REGISTRATION_FAILED,
116 cause.getMessage(), OAuth2Error.INVALID_REQUEST);
margaretha8d804f62018-04-10 12:39:56 +0200117 }
margaretha0e8f4e72018-04-05 14:11:52 +0200118
119 return new OAuth2ClientDto(id, secret);
120 }
121
122
margaretha6374f722018-04-17 18:45:57 +0200123 private boolean isNativeClient (String url, String redirectURI)
124 throws KustvaktException {
margarethad7cab212018-07-02 19:01:43 +0200125 if (url == null || url.isEmpty() || redirectURI == null
126 || redirectURI.isEmpty()) {
127 return false;
128 }
129
margaretha6374f722018-04-17 18:45:57 +0200130 String nativeHost = config.getNativeClientHost();
131 String urlHost = null;
132 try {
133 urlHost = new URL(url).getHost();
134 }
135 catch (MalformedURLException e) {
136 throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
137 "Invalid url :" + e.getMessage(),
margarethaa452c5e2018-04-25 22:48:09 +0200138 OAuth2Error.INVALID_REQUEST);
margaretha6374f722018-04-17 18:45:57 +0200139 }
margarethad7cab212018-07-02 19:01:43 +0200140
141 if (!urlHost.equals(nativeHost)) {
142 return false;
143 }
144
margaretha6374f722018-04-17 18:45:57 +0200145 String uriHost = null;
146 try {
147 uriHost = new URI(redirectURI).getHost();
148 }
149 catch (URISyntaxException e) {
150 throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
margarethafb027f92018-04-23 20:00:13 +0200151 "Invalid redirectURI: " + e.getMessage(),
margarethaa452c5e2018-04-25 22:48:09 +0200152 OAuth2Error.INVALID_REQUEST);
margaretha6374f722018-04-17 18:45:57 +0200153 }
margarethad7cab212018-07-02 19:01:43 +0200154 if (!uriHost.equals(nativeHost)) {
155 return false;
156 }
157
158 return true;
margaretha6374f722018-04-17 18:45:57 +0200159 }
160
161
margaretha80ea0dd2018-07-03 14:22:59 +0200162 public void deregisterClient (String clientId, String clientSecret,
163 String username) throws KustvaktException {
margaretha0e8f4e72018-04-05 14:11:52 +0200164
margarethafb027f92018-04-23 20:00:13 +0200165 OAuth2Client client = clientDao.retrieveClientById(clientId);
margaretha80ea0dd2018-07-03 14:22:59 +0200166 if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
167 authenticateClient(clientId, clientSecret);
margaretha8d804f62018-04-10 12:39:56 +0200168 }
margaretha80ea0dd2018-07-03 14:22:59 +0200169
170 if (adminDao.isAdmin(username)
171 || client.getRegisteredBy().equals(username)) {
margaretha8d804f62018-04-10 12:39:56 +0200172 clientDao.deregisterClient(client);
173 }
174 else {
175 throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
176 "Unauthorized operation for user: " + username, username);
177 }
margaretha0e8f4e72018-04-05 14:11:52 +0200178 }
179
margarethafb027f92018-04-23 20:00:13 +0200180 public OAuth2Client authenticateClient (String clientId,
181 String clientSecret) throws KustvaktException {
margaretha6374f722018-04-17 18:45:57 +0200182
margarethafb027f92018-04-23 20:00:13 +0200183 if (clientId == null || clientId.isEmpty()) {
margaretha05122312018-04-16 15:01:34 +0200184 throw new KustvaktException(
185 StatusCodes.CLIENT_AUTHENTICATION_FAILED,
margarethaa452c5e2018-04-25 22:48:09 +0200186 "Missing parameters: client id",
187 OAuth2Error.INVALID_REQUEST);
margarethaa0486272018-04-12 19:59:31 +0200188 }
margaretha0e8f4e72018-04-05 14:11:52 +0200189
margarethafb027f92018-04-23 20:00:13 +0200190 OAuth2Client client = clientDao.retrieveClientById(clientId);
margaretha80ea0dd2018-07-03 14:22:59 +0200191 authenticateClient(client, clientSecret);
192 return client;
193 }
194
195 public void authenticateClient (OAuth2Client client, String clientSecret)
196 throws KustvaktException {
197 if (clientSecret == null) {
198 if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
margaretha05122312018-04-16 15:01:34 +0200199 throw new KustvaktException(
200 StatusCodes.CLIENT_AUTHENTICATION_FAILED,
margarethaa452c5e2018-04-25 22:48:09 +0200201 "Missing parameters: client_secret",
202 OAuth2Error.INVALID_REQUEST);
margarethafb027f92018-04-23 20:00:13 +0200203 }
margarethafb027f92018-04-23 20:00:13 +0200204 }
margaretha80ea0dd2018-07-03 14:22:59 +0200205 else if (clientSecret.isEmpty()) {
206 if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
207 throw new KustvaktException(
208 StatusCodes.CLIENT_AUTHENTICATION_FAILED,
209 "Missing parameters: client_secret",
210 OAuth2Error.INVALID_REQUEST);
margaretha0e8f4e72018-04-05 14:11:52 +0200211 }
212 }
margaretha80ea0dd2018-07-03 14:22:59 +0200213 else if (!encryption.checkHash(clientSecret, client.getSecret(),
214 config.getPasscodeSaltField())) {
215 throw new KustvaktException(
216 StatusCodes.CLIENT_AUTHENTICATION_FAILED,
217 "Invalid client credentials", OAuth2Error.INVALID_CLIENT);
218 }
margaretha31a9f522018-04-03 20:40:45 +0200219 }
margarethafb1e0992018-04-10 14:58:28 +0200220
margaretha07402f42018-05-07 19:07:45 +0200221
222 public OAuth2Client authenticateClientId (String clientId)
223 throws KustvaktException {
224 if (clientId == null || clientId.isEmpty()) {
225 throw new KustvaktException(
226 StatusCodes.CLIENT_AUTHENTICATION_FAILED,
227 "Missing parameters: client id",
228 OAuth2Error.INVALID_REQUEST);
229 }
230
231 return clientDao.retrieveClientById(clientId);
232 }
margaretha31a9f522018-04-03 20:40:45 +0200233}