blob: 505140cb43534e7c7dbc49a486d3bc52c20c3548 [file] [log] [blame]
package de.ids_mannheim.korap.oauth2.service;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.validator.routines.UrlValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.ids_mannheim.korap.config.FullConfiguration;
import de.ids_mannheim.korap.dao.AdminDao;
import de.ids_mannheim.korap.encryption.RandomCodeGenerator;
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.AccessTokenDao;
import de.ids_mannheim.korap.oauth2.dao.AuthorizationDao;
import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
import de.ids_mannheim.korap.oauth2.dto.OAuth2ClientDto;
import de.ids_mannheim.korap.oauth2.dto.OAuth2ClientInfoDto;
import de.ids_mannheim.korap.oauth2.dto.OAuth2UserClientDto;
import de.ids_mannheim.korap.oauth2.entity.AccessToken;
import de.ids_mannheim.korap.oauth2.entity.Authorization;
import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
import de.ids_mannheim.korap.utils.ParameterChecker;
import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
/**
* Defines business logic related to OAuth2 client including
* client registration and client authentication.
*
* 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
* requirements),
* </li>
*
* <li>authenticate the client if client authentication is included
* </li>
* </ul>
*
* @author margaretha
*
*/
@Service
public class OAuth2ClientService {
@Autowired
private OAuth2ClientDao clientDao;
@Autowired
private AccessTokenDao tokenDao;
@Autowired
private RefreshTokenDao refreshDao;
@Autowired
private AuthorizationDao authorizationDao;
@Autowired
private AdminDao adminDao;
@Autowired
private UrlValidator redirectURIValidator;
@Autowired
private UrlValidator urlValidator;
@Autowired
private EncryptionIface encryption;
@Autowired
private RandomCodeGenerator codeGenerator;
@Autowired
private FullConfiguration config;
public OAuth2ClientDto registerClient (OAuth2ClientJson clientJson,
String registeredBy) throws KustvaktException {
try {
ParameterChecker.checkNameValue(clientJson.getName(), "client_name");
ParameterChecker.checkObjectValue(clientJson.getType(), "client_type");
}
catch (KustvaktException e) {
throw new KustvaktException(e.getStatusCode(), e.getMessage(),
OAuth2Error.INVALID_REQUEST);
}
String url = clientJson.getUrl();
if (url != null && !url.isEmpty()) {
if (!urlValidator.isValid(url)) {
throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
url + " is invalid.", OAuth2Error.INVALID_REQUEST);
}
}
String redirectURI = clientJson.getRedirectURI();
if (redirectURI != null && !redirectURI.isEmpty()
&& !redirectURIValidator.isValid(redirectURI)) {
throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
redirectURI + " is invalid.", OAuth2Error.INVALID_REQUEST);
}
// boolean isNative = isNativeClient(url, redirectURI);
String secret = null;
String secretHashcode = null;
if (clientJson.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
// RFC 6749:
// The authorization server MUST NOT issue client
// passwords or other client credentials to native
// application (clients installed and executed on the
// device used by the resource owner e.g. desktop
// application, native mobile application) or
// user-agent-based application clients for client
// authentication. The authorization server MAY issue a
// client password or other credentials for a specific
// installation of a native application client on a
// specific device.
secret = codeGenerator.createRandomCode();
secretHashcode = encryption.secureHash(secret);
}
String id = codeGenerator.createRandomCode();
id = codeGenerator.filterRandomCode(id);
try {
clientDao.registerClient(id, secretHashcode, clientJson.getName(),
clientJson.getType(), url, redirectURI, registeredBy,
clientJson.getDescription());
}
catch (KustvaktException e) {
throw new KustvaktException(e.getStatusCode(),
e.getMessage(), OAuth2Error.INVALID_REQUEST);
}
catch (Exception e) {
Throwable cause = e;
Throwable lastCause = null;
while ((cause = cause.getCause()) != null
&& !cause.equals(lastCause)) {
if (cause instanceof SQLException) {
break;
}
lastCause = cause;
}
throw new KustvaktException(StatusCodes.CLIENT_REGISTRATION_FAILED,
cause.getMessage(), OAuth2Error.INVALID_REQUEST);
}
return new OAuth2ClientDto(id, secret);
}
@Deprecated
private boolean isNativeClient (String url, String redirectURI)
throws KustvaktException {
if (url == null || url.isEmpty() || redirectURI == null
|| redirectURI.isEmpty()) {
return false;
}
String nativeHost = config.getNativeClientHost();
String urlHost = null;
try {
urlHost = new URL(url).getHost();
}
catch (MalformedURLException e) {
throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
"Invalid url :" + e.getMessage(),
OAuth2Error.INVALID_REQUEST);
}
if (!urlHost.equals(nativeHost)) {
return false;
}
String uriHost = null;
try {
uriHost = new URI(redirectURI).getHost();
}
catch (URISyntaxException e) {
throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
"Invalid redirectURI: " + e.getMessage(),
OAuth2Error.INVALID_REQUEST);
}
if (!uriHost.equals(nativeHost)) {
return false;
}
return true;
}
public void deregisterClient (String clientId, String username)
throws KustvaktException {
OAuth2Client client = clientDao.retrieveClientById(clientId);
if (adminDao.isAdmin(username)
|| client.getRegisteredBy().equals(username)) {
revokeAllAuthorizationsByClientId(clientId);
clientDao.deregisterClient(client);
}
else {
throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
"Unauthorized operation for user: " + username, username);
}
}
public void revokeAllAuthorizationsByClientId (String clientId)
throws KustvaktException {
// revoke all related authorization codes
List<Authorization> authList =
authorizationDao.retrieveAuthorizationsByClientId(clientId);
for (Authorization authorization : authList) {
authorization.setRevoked(true);
authorizationDao.updateAuthorization(authorization);
}
// revoke all related access tokens
List<AccessToken> tokens =
tokenDao.retrieveAccessTokenByClientId(clientId,null);
for (AccessToken token : tokens) {
token.setRevoked(true);
tokenDao.updateAccessToken(token);
}
List<RefreshToken> refreshTokens =
refreshDao.retrieveRefreshTokenByClientId(clientId,null);
for (RefreshToken token : refreshTokens) {
token.setRevoked(true);
refreshDao.updateRefreshToken(token);
}
}
public OAuth2ClientDto resetSecret (String clientId, String username)
throws KustvaktException {
OAuth2Client client = clientDao.retrieveClientById(clientId);
if (!client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
throw new KustvaktException(StatusCodes.NOT_ALLOWED,
"Operation is not allowed for public clients",
OAuth2Error.INVALID_REQUEST);
}
if (adminDao.isAdmin(username)
|| client.getRegisteredBy().equals(username)) {
String secret = codeGenerator.createRandomCode();
String secretHashcode = encryption.secureHash(secret);
client.setSecret(secretHashcode);
clientDao.updateClient(client);
return new OAuth2ClientDto(clientId, secret);
}
else {
throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
"Unauthorized operation for user: " + username, username);
}
}
public OAuth2Client authenticateClient (String clientId,
String clientSecret) throws KustvaktException {
if (clientId == null || clientId.isEmpty()) {
throw new KustvaktException(
StatusCodes.CLIENT_AUTHENTICATION_FAILED,
"Missing parameters: client id",
OAuth2Error.INVALID_REQUEST);
}
OAuth2Client client = clientDao.retrieveClientById(clientId);
authenticateClient(client, clientSecret);
return client;
}
public void authenticateClient (OAuth2Client client, String clientSecret)
throws KustvaktException {
if (clientSecret == null || clientSecret.isEmpty()) {
if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
throw new KustvaktException(
StatusCodes.CLIENT_AUTHENTICATION_FAILED,
"Missing parameters: client_secret",
OAuth2Error.INVALID_REQUEST);
}
}
else if (client.getSecret() == null || client.getSecret().isEmpty()) {
if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
throw new KustvaktException(
StatusCodes.CLIENT_AUTHENTICATION_FAILED,
"Client secret was not registered",
OAuth2Error.INVALID_CLIENT);
}
}
else if (!encryption.checkHash(clientSecret, client.getSecret())) {
throw new KustvaktException(
StatusCodes.CLIENT_AUTHENTICATION_FAILED,
"Invalid client credentials", OAuth2Error.INVALID_CLIENT);
}
}
public OAuth2Client authenticateClientId (String clientId)
throws KustvaktException {
if (clientId == null || clientId.isEmpty()) {
throw new KustvaktException(
StatusCodes.CLIENT_AUTHENTICATION_FAILED,
"Missing parameters: client id",
OAuth2Error.INVALID_REQUEST);
}
return clientDao.retrieveClientById(clientId);
}
public OAuth2ClientInfoDto retrieveClientInfo (String username,
String clientId) throws KustvaktException {
OAuth2Client client = clientDao.retrieveClientById(clientId);
if (adminDao.isAdmin(username)
|| username.equals(client.getRegisteredBy())) {
return new OAuth2ClientInfoDto(client);
}
else {
throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
"Unauthorized operation for user: " + username, username);
}
}
public OAuth2Client retrieveClient (String clientId)
throws KustvaktException {
return clientDao.retrieveClientById(clientId);
}
public List<OAuth2UserClientDto> listUserAuthorizedClients (String username,
String superClientId, String superClientSecret) throws KustvaktException {
OAuth2Client superClient = authenticateClient(superClientId, superClientSecret);
if (!superClient.isSuper()) {
throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
"Only super client is allowed to list user authorized clients.",
OAuth2Error.UNAUTHORIZED_CLIENT);
}
List<OAuth2Client> userClients =
clientDao.retrieveUserAuthorizedClients(username);
userClients.addAll(clientDao.retrieveClientsByAccessTokens(username));
List<String> clientIds = new ArrayList<>();
List<OAuth2Client> uniqueClients = new ArrayList<>();
for (OAuth2Client c : userClients){
String id = c.getId();
if (!clientIds.contains(id)){
clientIds.add(id);
uniqueClients.add(c);
}
}
Collections.sort(uniqueClients);
return createClientDtos(uniqueClients);
}
public List<OAuth2UserClientDto> listUserRegisteredClients (String username,
String clientId, String clientSecret) throws KustvaktException {
OAuth2Client client = authenticateClient(clientId, clientSecret);
if (!client.isSuper()) {
throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
"Only super client is allowed to list user registered clients.",
OAuth2Error.UNAUTHORIZED_CLIENT);
}
List<OAuth2Client> userClients =
clientDao.retrieveUserRegisteredClients(username);
Collections.sort(userClients);
return createClientDtos(userClients);
}
private List<OAuth2UserClientDto> createClientDtos (List<OAuth2Client> userClients) {
List<OAuth2UserClientDto> dtoList = new ArrayList<>(userClients.size());
for (OAuth2Client uc : userClients) {
if (uc.isSuper()) continue;
OAuth2UserClientDto dto = new OAuth2UserClientDto();
dto.setClientId(uc.getId());
dto.setClientName(uc.getName());
dto.setDescription(uc.getDescription());
dto.setUrl(uc.getUrl());
dto.setClientType(uc.getType());
dtoList.add(dto);
}
return dtoList;
}
public boolean isPublicClient (OAuth2Client oAuth2Client) {
return oAuth2Client.getType().equals(OAuth2ClientType.PUBLIC);
}
}