
org.osiam.client.oauth.AuthService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of connector4java Show documentation
Show all versions of connector4java Show documentation
Native Java API to connect to the REST based OSIAM services
/*
* Copyright (C) 2013 tarent AG
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.osiam.client.oauth;
import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
import static org.apache.http.HttpStatus.SC_NOT_FOUND;
import static org.apache.http.HttpStatus.SC_OK;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.osiam.client.exception.ConflictException;
import org.osiam.client.exception.ConnectionInitializationException;
import org.osiam.client.exception.ForbiddenException;
import org.osiam.client.exception.InvalidAttributeException;
import org.osiam.client.exception.OsiamErrorMessage;
import org.osiam.client.exception.UnauthorizedException;
/**
* The AuthService provides access to the OAuth2 service used to authorize requests. Please use the
* {@link AuthService.Builder} to construct one.
*/
public final class AuthService { // NOSONAR - Builder constructs instances of this class
private static final Charset CHARSET = Charset.forName("UTF-8");
private HttpPost post;
private final String endpoint;
private Header[] headers;
private String clientId;
private String clientSecret;
private String clientRedirectUri;
private String scopes;
private String password;
private String userName;
private GrantType grantType;
private HttpEntity body;
/**
* The private constructor for the AuthService. Please use the {@link AuthService.Builder}
* to construct one.
*
* @param builder a valid Builder that holds all needed variables
*/
private AuthService(Builder builder) {
endpoint = builder.endpoint;
scopes = builder.scopes;
grantType = builder.grantType;
userName = builder.userName;
password = builder.password;
clientId = builder.clientId;
clientSecret = builder.clientSecret;
clientRedirectUri = builder.clientRedirectUri;
}
private HttpResponse performRequest(AccessToken... accessTokens) {
buildHead();
buildBody(accessTokens);
post = new HttpPost(getFinalEndpoint());
post.setHeaders(headers);
post.setEntity(body);
HttpClient defaultHttpClient = new DefaultHttpClient();
final HttpResponse response;
try {
response = defaultHttpClient.execute(post);
} catch (IOException e) {
throw new ConnectionInitializationException("Unable to perform Request ", e);
}
return response;
}
private void buildHead() {
String authHeaderValue = "Basic " + encodeClientCredentials(clientId, clientSecret);
Header authHeader = new BasicHeader("Authorization", authHeaderValue);
headers = new Header[]{
authHeader
};
}
private String encodeClientCredentials(String clientId, String clientSecret) {
String clientCredentials = clientId + ":" + clientSecret;
clientCredentials = new String(Base64.encodeBase64(clientCredentials.getBytes(CHARSET)), CHARSET);
return clientCredentials;
}
private void buildBody(AccessToken... accessTokens) {
List nameValuePairs = new ArrayList<>();
nameValuePairs.add(new BasicNameValuePair("scope", scopes));
nameValuePairs.add(new BasicNameValuePair("grant_type", grantType.getUrlParam())); // NOSONAR - we check before that the grantType is not null
if(grantType != GrantType.REFRESH_TOKEN) {
if(userName != null){
nameValuePairs.add(new BasicNameValuePair("username", userName));
}
if(password != null){
nameValuePairs.add(new BasicNameValuePair("password", password));
}
} else if (grantType == GrantType.REFRESH_TOKEN && accessTokens.length != 0) {
if (accessTokens[0].getRefreshToken() == null) {
throw new ConnectionInitializationException("Unable to perform a refresh_token_grant request without refresh token.");
}
nameValuePairs.add(new BasicNameValuePair("refresh_token", accessTokens[0].getRefreshToken()));
}
try {
body = new UrlEncodedFormEntity(nameValuePairs);
} catch (UnsupportedEncodingException e) {
throw new ConnectionInitializationException("Unable to Build Request in this encoding.", e);
}
}
/**
* Provide an {@link AccessToken} for the given parameters of this service.
*
* @return a valid AccessToken
* @throws ConnectionInitializationException
* If the Service is unable to connect to the configured OAuth2 service.
* @throws UnauthorizedException If the configured credentials for this service are not permitted
* to retrieve an {@link AccessToken}
*/
public AccessToken retrieveAccessToken() {
if(grantType == GrantType.AUTHORIZATION_CODE){
throw new IllegalAccessError("For the grant type " + GrantType.AUTHORIZATION_CODE
+ " you need to retrieve a authentication code first.");
}
HttpResponse response = performRequest();
int status = response.getStatusLine().getStatusCode();
checkAndHandleHttpStatus(response, status);
return getAccessToken(response);
}
private void checkAndHandleHttpStatus(HttpResponse response, int status) {
if (status != SC_OK) {
String errorMessage;
String defaultMessage;
switch (status) {
case SC_BAD_REQUEST:
defaultMessage = "Unable to create Connection. Please make sure that you have the correct grants.";
errorMessage = getErrorMessage(response, defaultMessage);
throw new ConnectionInitializationException(errorMessage);
case SC_UNAUTHORIZED:
defaultMessage = "You are not authorized to directly retrieve a access token.";
errorMessage = getErrorMessage(response, defaultMessage);
throw new UnauthorizedException(errorMessage);
case SC_NOT_FOUND:
defaultMessage = "Unable to find the given OSIAM service (" + getFinalEndpoint() + ")";
errorMessage = getErrorMessage(response, defaultMessage);
throw new ConnectionInitializationException(errorMessage);
default:
defaultMessage = String.format("Unable to setup connection (HTTP Status Code: %d)", status);
errorMessage = getErrorMessage(response, defaultMessage);
throw new ConnectionInitializationException(errorMessage);
}
}
}
private String getErrorMessage(HttpResponse httpResponse, String defaultErrorMessage) {
InputStream content = null;
String errorMessage;
try{
content = httpResponse.getEntity().getContent();
ObjectMapper mapper = new ObjectMapper();
OsiamErrorMessage error = mapper.readValue(content, OsiamErrorMessage.class);
errorMessage = error.getDescription();
} catch (Exception e){ // NOSONAR - we catch everything
errorMessage = defaultErrorMessage;
}finally{
try{
if(content != null) { content.close(); }
}catch(IOException notNeeded){/**doesn't matter**/}
}
if(errorMessage == null){
errorMessage = defaultErrorMessage;
}
return errorMessage;
}
/**
* provides the needed URI which is needed to reconnect the User to the OSIAM server to login.
* A detailed example how to use this method, can be seen in our wiki in gitHub
* @return the needed redirect Uri
* @see https://github.com/osiam/connector4java/wiki/Login-and-getting-an-access-token#grant-authorization-code
*/
public URI getRedirectLoginUri() {
if(grantType != GrantType.AUTHORIZATION_CODE){
throw new IllegalAccessError("You need to use the GrantType " + GrantType.AUTHORIZATION_CODE
+ " to be able to use this method.");
}
URI returnUri;
try {
returnUri = new URIBuilder().setPath(getFinalEndpoint())
.addParameter("client_id", clientId)
.addParameter("response_type", "code")
.addParameter("redirect_uri", clientRedirectUri)
.addParameter("scope", scopes)
.build();
} catch (URISyntaxException e){
throw new ConnectionInitializationException("Unable to create redirect URI", e);
}
return returnUri;
}
/**
* Provide an {@link AccessToken} for the given parameters of this service and the given {@link HttpResponse}.
* If the User acepted your request for the needed data you will get an access token.
* If the User denied your request a {@link ForbiddenException} will be thrown.
* If the {@linkplain HttpResponse} does not contain a value named "code" or "error" a
* {@linkplain InvalidAttributeException} will be thrown
* @param authCodeResponse response given from the OSIAM server.
* For more information please look at the wiki at github
* @return a valid AccessToken
* @throws org.osiam.client.exception.ForbiddenException in case the User had denied you the wanted data
* @throws org.osiam.client.exception.InvalidAttributeException in case not authCode and no error message could be found in the response
* @throws org.osiam.client.exception.ConflictException in case the given authCode could not be exchanged against a access token
* @throws org.osiam.client.exception.ConnectionInitializationException
* If the Service is unable to connect to the configured OAuth2 service.
* @see https://github.com/osiam/connector4java/wiki/Login-and-getting-an-access-token#grant-authorization-code
*/
public AccessToken retrieveAccessToken(HttpResponse authCodeResponse) {
String authCode = null;
Header header = authCodeResponse.getLastHeader("Location");
HeaderElement[] elements = header.getElements();
for (HeaderElement actHeaderElement : elements) {
if(actHeaderElement.getName().contains("code")){
authCode = actHeaderElement.getValue();
break;
}
if(actHeaderElement.getName().contains("error")){
throw new ForbiddenException("The user had denied the acces to his data.");
}
}
if(authCode == null){
throw new InvalidAttributeException("Could not find any auth code or error message in the given Response");
}
return retrieveAccessToken(authCode);
}
/**
* Provide an {@link AccessToken} for the given parameters of this service and the given authCode.
* @param authCode authentication code retrieved from the OSIAM Server by using the oauth2 login flow.
* For more information please look at the wiki at github
* @return a valid AccessToken
* @throws ConflictException in case the given authCode could not be exchanged against a access token
* @throws ConnectionInitializationException
* If the Service is unable to connect to the configured OAuth2 service.
* @see https://github.com/osiam/connector4java/wiki/Login-and-getting-an-access-token#grant-authorization-code
*/
public AccessToken retrieveAccessToken(String authCode) {
if (authCode == null) {
throw new IllegalArgumentException("The given authentication code can't be null.");
}
HttpPost realWebResource = getWebRessourceToEchangeAuthCode(authCode);
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpResponse response;
int httpStatus;
try{
response = httpClient.execute(realWebResource);
httpStatus = response.getStatusLine().getStatusCode();
}catch(IOException e){
throw new ConnectionInitializationException("Unable to setup connection", e);
}
if (httpStatus != SC_OK) {
String errorMessage;
switch (httpStatus) {
case SC_BAD_REQUEST:
errorMessage = getErrorMessage(response, "Could not exchange yout authentication code against a access token.");
throw new ConflictException(errorMessage);
default:
errorMessage = getErrorMessage(response, String.format("Unable to setup connection (HTTP Status Code: %d)", httpStatus));
throw new ConnectionInitializationException(errorMessage);
}
}
return getAccessToken(response);
}
private AccessToken getAccessToken(HttpResponse response){
final AccessToken accessToken;
try {
InputStream content = response.getEntity().getContent();
ObjectMapper mapper = new ObjectMapper();
accessToken = mapper.readValue(content, AccessToken.class);
} catch (IOException e) {
throw new ConnectionInitializationException("Unable to retrieve access token: IOException", e);
}
return accessToken;
}
private String getFinalEndpoint(){
String finalEndpoint = endpoint;
if(grantType.equals(GrantType.AUTHORIZATION_CODE)){// NOSONAR - we check before that the grantType is not null
finalEndpoint += "/oauth/authorize";
}else{
finalEndpoint += "/oauth/token";
}
return finalEndpoint;
}
private HttpPost getWebRessourceToEchangeAuthCode(String authCode){
HttpPost realWebResource = new HttpPost(endpoint + "/oauth/token");
String authHeaderValue = "Basic " + encodeClientCredentials(clientId, clientSecret);
realWebResource.addHeader("Authorization", authHeaderValue);
List nameValuePairs = new ArrayList<>();
nameValuePairs.add(new BasicNameValuePair("code", authCode));
nameValuePairs.add(new BasicNameValuePair("grant_type", "authorization_code"));
nameValuePairs.add(new BasicNameValuePair("redirect_uri", clientRedirectUri));
try{
realWebResource.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8"));
}catch(UnsupportedEncodingException e){
throw new ConnectionInitializationException("Unable to Build Request in this encoding.", e);
}
return realWebResource;
}
public AccessToken refreshAccessToken(AccessToken accessToken, Scope[] scopes) {
if (scopes.length != 0) {
StringBuilder stringBuilder = new StringBuilder();
for(Scope scope : scopes) {
stringBuilder.append(" ").append(scope.toString());
}
this.scopes = stringBuilder.toString().trim();
}
this.grantType = GrantType.REFRESH_TOKEN;
HttpResponse response = performRequest(accessToken);
int status = response.getStatusLine().getStatusCode();
checkAndHandleHttpStatus(response, status);
return getAccessToken(response);
}
/**
* The Builder class is used to construct instances of the {@link AuthService}.
*/
public static class Builder {
private String clientId;
private String clientSecret;
private GrantType grantType;
private String scopes;
private String endpoint;
private String password;
private String userName;
private String clientRedirectUri;
/**
* Set up the Builder for the construction of an {@link AuthService} instance for the OAuth2 service at
* the given endpoint
*
* @param endpoint The URL at which the OAuth2 service lives.
*/
public Builder(String endpoint) {
this.endpoint = endpoint;
}
/**
* Use the given {@link Scope} to for the request.
* @param scope the needed scope
* @param scopes the needed scopes
* @return The builder itself
*/
public Builder setScope(Scope scope, Scope... scopes){
Set scopeSet = new HashSet<>();
scopeSet.add(scope);
for (Scope actScope: scopes) {
scopeSet.add(actScope);
}
if(scopeSet.contains(Scope.ALL)){
this.scopes = Scope.ALL.toString();
}else{
StringBuilder scopeBuilder = new StringBuilder();
for (Scope actScope : scopeSet) {
scopeBuilder.append(" ").append(actScope.toString());
}
this.scopes = scopeBuilder.toString().trim();
}
return this;
}
/**
* The needed access token scopes as String like 'GET PATCH'
* @param scope the needed scopes
* @return The builder itself
*/
public Builder setScope(String scope){
this.scopes = scope;
return this;
}
/**
* Use the given {@link GrantType} to for the request.
*
* @param grantType of the requested AuthCode
* @return The builder itself
*/
public Builder setGrantType(GrantType grantType) {
this.grantType = grantType;
return this;
}
/**
* Add a ClientId to the OAuth2 request
*
* @param clientId The client-Id
* @return The builder itself
*/
public Builder setClientId(String clientId) {
this.clientId = clientId;
return this;
}
/**
* Add a Client redirect URI to the OAuth2 request
*
* @param clientRedirectUri the clientRedirectUri which is known to the OSIAM server
* @return The builder itself
*/
public Builder setClientRedirectUri(String clientRedirectUri) {
this.clientRedirectUri = clientRedirectUri;
return this;
}
/**
* Add a clientSecret to the OAuth2 request
*
* @param clientSecret The client secret
* @return The builder itself
*/
public Builder setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}
/**
* Add the given userName to the OAuth2 request
*
* @param userName The userName
* @return The builder itself
*/
public Builder setUsername(String userName) {
this.userName = userName;
return this;
}
/**
* Add the given password to the OAuth2 request
*
* @param password The password
* @return The builder itself
*/
public Builder setPassword(String password) {
this.password = password;
return this;
}
/**
* Construct the {@link AuthService} with the parameters passed to this builder.
*
* @return An AuthService configured accordingly.
*/
public AuthService build() {
ensureAllNeededParameterAreCorrect();
return new AuthService(this);
}
private void ensureAllNeededParameterAreCorrect(){// NOSONAR - this is a test method the Cyclomatic Complexity can be over 10.
if (clientId == null || clientSecret == null) {
throw new IllegalArgumentException("The provided client credentials are incomplete.");
}
if(scopes == null){
throw new IllegalArgumentException("At least one scope needs to be set.");
}
if (grantType == null) {
throw new IllegalArgumentException("The grant type is not set.");
}
if (grantType.equals(GrantType.RESOURCE_OWNER_PASSWORD_CREDENTIALS) && (userName == null && password == null)) {
throw new IllegalArgumentException("The grant type 'password' requires username and password");
}
if ((grantType.equals(GrantType.CLIENT_CREDENTIALS) || grantType.equals(GrantType.AUTHORIZATION_CODE))
&& (userName != null || password != null)) {
throw new IllegalArgumentException("For the grant type '" + grantType + "' setting of password and username are not allowed.");
}
if (grantType.equals(GrantType.AUTHORIZATION_CODE) && clientRedirectUri == null) {
throw new IllegalArgumentException("For the grant type '" + grantType + "' the redirect Uri is needed.");
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy