All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.gravitee.am.identityprovider.http.user.HttpUserProvider Maven / Gradle / Ivy

There is a newer version: 4.6.0-alpha.2
Show newest version
/**
 * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gravitee.am.identityprovider.http.user;

import io.gravitee.am.identityprovider.api.AuthenticationContext;
import io.gravitee.am.identityprovider.api.DefaultUser;
import io.gravitee.am.identityprovider.api.IdentityProviderMapper;
import io.gravitee.am.identityprovider.api.SimpleAuthenticationContext;
import io.gravitee.am.identityprovider.api.User;
import io.gravitee.am.identityprovider.api.UserProvider;
import io.gravitee.am.identityprovider.http.HttpIdentityProviderResponse;
import io.gravitee.am.identityprovider.http.configuration.HttpIdentityProviderConfiguration;
import io.gravitee.am.identityprovider.http.configuration.HttpResourceConfiguration;
import io.gravitee.am.identityprovider.http.configuration.HttpResponseErrorCondition;
import io.gravitee.am.identityprovider.http.configuration.HttpUsersResourceConfiguration;
import io.gravitee.am.identityprovider.http.user.spring.HttpUserProviderConfiguration;
import io.gravitee.am.identityprovider.http.utils.SanitizeUtils;
import io.gravitee.am.service.authentication.crypto.password.PasswordEncoder;
import io.gravitee.am.service.exception.AbstractManagementException;
import io.gravitee.am.service.exception.TechnicalManagementException;
import io.gravitee.common.http.HttpHeader;
import io.gravitee.common.http.HttpHeaders;
import io.gravitee.common.http.MediaType;
import io.gravitee.el.TemplateEngine;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.rxjava3.core.MultiMap;
import io.vertx.rxjava3.core.buffer.Buffer;
import io.vertx.rxjava3.ext.web.client.HttpRequest;
import io.vertx.rxjava3.ext.web.client.HttpResponse;
import io.vertx.rxjava3.ext.web.client.WebClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Import;
import org.springframework.util.StringUtils;

import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static java.util.Optional.ofNullable;

/**
 * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
 * @author GraviteeSource Team
 */
@Import({HttpUserProviderConfiguration.class})
public class HttpUserProvider implements UserProvider {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpUserProvider.class);
    private static final String USER_CONTEXT_KEY = "user";
    private static final String USER_API_RESPONSE_CONTEXT_KEY = "usersResponse";

    @Autowired
    @Qualifier("idpHttpUsersWebClient")
    private WebClient client;

    @Autowired
    private HttpIdentityProviderConfiguration configuration;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private IdentityProviderMapper mapper;

    @Override
    public Maybe findByEmail(String email) {
        // prepare request
        final HttpUsersResourceConfiguration usersResourceConfiguration = configuration.getUsersResource();
        final HttpResourceConfiguration readResourceConfiguration = usersResourceConfiguration.getPaths().getReadResourceByEmail();
        final DefaultUser user = new DefaultUser();
        user.setEmail(email);

        return findByUser(usersResourceConfiguration, readResourceConfiguration, user);
    }

    @Override
    public Maybe findByUsername(String username) {
        // prepare request
        final HttpUsersResourceConfiguration usersResourceConfiguration = configuration.getUsersResource();
        final HttpResourceConfiguration readResourceConfiguration = usersResourceConfiguration.getPaths().getReadResource();
        final DefaultUser user = new DefaultUser(username);

        return findByUser(usersResourceConfiguration, readResourceConfiguration, user);
    }

    @Override
    public Single create(User user) {
        try {
            // prepare request
            final HttpUsersResourceConfiguration usersResourceConfiguration = configuration.getUsersResource();
            final HttpResourceConfiguration createResourceConfiguration = usersResourceConfiguration.getPaths().getCreateResource();
            final String createUserURI = usersResourceConfiguration.getBaseURL() + createResourceConfiguration.getBaseURL();
            final HttpMethod createUserHttpMethod = HttpMethod.valueOf(createResourceConfiguration.getHttpMethod().toString());
            final List createUserHttpHeaders = createResourceConfiguration.getHttpHeaders();
            final String createUserBody = createResourceConfiguration.getHttpBody();

            // prepare context
            AuthenticationContext authenticationContext = new SimpleAuthenticationContext();
            TemplateEngine templateEngine = authenticationContext.getTemplateEngine();
            // sanitize password
            if (!StringUtils.isEmpty(user.getCredentials())) {
                ((DefaultUser) user).setCredentials(SanitizeUtils.sanitize(passwordEncoder.encode(user.getCredentials()), createUserBody, createUserHttpHeaders));
            }
            templateEngine.getTemplateContext().setVariable(USER_CONTEXT_KEY, user);

            // process request
            final Single> requestHandler = processRequest(templateEngine, createUserURI, createUserHttpMethod, createUserHttpHeaders, createUserBody);

            return requestHandler
                    .map(httpResponse -> {
                        final List errorConditions = createResourceConfiguration.getHttpResponseErrorConditions();
                        Map userAttributes = processResponse(templateEngine, errorConditions, httpResponse);
                        return convert(user.getUsername(), userAttributes);
                    })
                    .onErrorResumeNext(ex -> {
                        if (ex instanceof AbstractManagementException) {
                            return Single.error(ex);
                        }
                        LOGGER.error("An error has occurred while creating user {} from the remote HTTP identity provider", user.getUsername(), ex);
                        return Single.error(new TechnicalManagementException("An error has occurred while creating user from the remote HTTP identity provider", ex));
                    });
        } catch (Exception ex) {
            LOGGER.error("An error has occurred while creating the user {}", user.getUsername(), ex);
            return Single.error(new TechnicalManagementException("An error has occurred while creating the user", ex));
        }
    }

    @Override
    public Single update(String id, User updateUser) {
        try {
            // prepare request
            final HttpUsersResourceConfiguration usersResourceConfiguration = configuration.getUsersResource();
            final HttpResourceConfiguration updateResourceConfiguration = usersResourceConfiguration.getPaths().getUpdateResource();
            final String updateUserURI = usersResourceConfiguration.getBaseURL() + updateResourceConfiguration.getBaseURL();
            final HttpMethod updateUserHttpMethod = HttpMethod.valueOf(updateResourceConfiguration.getHttpMethod().toString());
            final List updateUserHttpHeaders = updateResourceConfiguration.getHttpHeaders();
            final String updateUserBody = updateResourceConfiguration.getHttpBody();

            // prepare context
            AuthenticationContext authenticationContext = new SimpleAuthenticationContext();
            TemplateEngine templateEngine = authenticationContext.getTemplateEngine();
            ((DefaultUser) updateUser).setId(id);
            // sanitize password
            if (!StringUtils.isEmpty(updateUser.getCredentials())) {
                ((DefaultUser) updateUser).setCredentials(SanitizeUtils.sanitize(passwordEncoder.encode(updateUser.getCredentials()), updateUserBody, updateUserHttpHeaders));
            }
            templateEngine.getTemplateContext().setVariable(USER_CONTEXT_KEY, updateUser);

            // process request
            final Single> requestHandler = processRequest(templateEngine, updateUserURI, updateUserHttpMethod, updateUserHttpHeaders, updateUserBody);

            return requestHandler
                    .map(httpResponse -> {
                        final List errorConditions = updateResourceConfiguration.getHttpResponseErrorConditions();
                        Map userAttributes = processResponse(templateEngine, errorConditions, httpResponse);
                        return convert(updateUser.getUsername(), userAttributes);
                    })
                    .onErrorResumeNext(ex -> {
                        if (ex instanceof AbstractManagementException) {
                            return Single.error(ex);
                        }
                        LOGGER.error("An error has occurred while updating user {} from the remote HTTP identity provider", updateUser.getUsername(), ex);
                        return Single.error(new TechnicalManagementException("An error has occurred while updating user from the remote HTTP identity provider", ex));
                    });
        } catch (Exception ex) {
            LOGGER.error("An error has occurred while updating the user {}", updateUser.getUsername(), ex);
            return Single.error(new TechnicalManagementException("An error has occurred while updating the user", ex));
        }
    }

    @Override
    public Completable delete(String id) {
        try {
            // prepare context
            DefaultUser deleteUser = new DefaultUser();
            deleteUser.setId(id);
            AuthenticationContext authenticationContext = new SimpleAuthenticationContext();
            TemplateEngine templateEngine = authenticationContext.getTemplateEngine();
            templateEngine.getTemplateContext().setVariable(USER_CONTEXT_KEY, deleteUser);

            // prepare request
            final HttpUsersResourceConfiguration usersResourceConfiguration = configuration.getUsersResource();
            final HttpResourceConfiguration deleteResourceConfiguration = usersResourceConfiguration.getPaths().getDeleteResource();
            final String deleteUserURI = usersResourceConfiguration.getBaseURL() + deleteResourceConfiguration.getBaseURL();
            final HttpMethod deleteUserHttpMethod = HttpMethod.valueOf(deleteResourceConfiguration.getHttpMethod().toString());
            final List deleteUserHttpHeaders = deleteResourceConfiguration.getHttpHeaders();
            final String updateUserBody = deleteResourceConfiguration.getHttpBody();
            final Single> requestHandler = processRequest(templateEngine, deleteUserURI, deleteUserHttpMethod, deleteUserHttpHeaders, updateUserBody);

            return requestHandler
                    .flatMapCompletable(httpResponse -> {
                        final List errorConditions = deleteResourceConfiguration.getHttpResponseErrorConditions();
                        try {
                            processResponse(templateEngine, errorConditions, httpResponse);
                            return Completable.complete();
                        } catch (Exception ex) {
                            return Completable.error(ex);
                        }
                    })
                    .onErrorResumeNext(ex -> {
                        if (ex instanceof AbstractManagementException) {
                            return Completable.error(ex);
                        }
                        LOGGER.error("An error has occurred while deleting user {} from the remote HTTP identity provider", id, ex);
                        return Completable.error(new TechnicalManagementException("An error has occurred while deleting user from the remote HTTP identity provider", ex));
                    });
        } catch (Exception ex) {
            LOGGER.error("An error has occurred while deleting the user {}", id, ex);
            return Completable.error(new TechnicalManagementException("An error has occurred while deleting the user", ex));
        }
    }

    private Maybe findByUser(HttpUsersResourceConfiguration usersResourceConfiguration,
                                   HttpResourceConfiguration readResourceConfiguration,
                                   User user) {
        try {
            // prepare context
            AuthenticationContext authenticationContext = new SimpleAuthenticationContext();
            TemplateEngine templateEngine = authenticationContext.getTemplateEngine();
            templateEngine.getTemplateContext().setVariable(USER_CONTEXT_KEY, user);

            // prepare request
            final String readUserURI = usersResourceConfiguration.getBaseURL() + readResourceConfiguration.getBaseURL();
            final HttpMethod readUserHttpMethod = HttpMethod.valueOf(readResourceConfiguration.getHttpMethod().toString());
            final List readUserHttpHeaders = readResourceConfiguration.getHttpHeaders();
            final String readUserBody = readResourceConfiguration.getHttpBody();
            final Single> requestHandler = processRequest(templateEngine, readUserURI, readUserHttpMethod, readUserHttpHeaders, readUserBody);

            return requestHandler
                    .toMaybe()
                    .map(httpResponse -> {
                        final List errorConditions = readResourceConfiguration.getHttpResponseErrorConditions();
                        Map userAttributes = processResponse(templateEngine, errorConditions, httpResponse);
                        return convert(null, userAttributes); // on find by user we do not force the username and use the payload as source of truth.
                    })
                    .onErrorResumeNext(ex -> {
                        if (ex instanceof AbstractManagementException) {
                            return Maybe.error(ex);
                        }
                        LOGGER.error("An error has occurred while searching user {} from the remote HTTP identity provider", user.getUsername() != null ? user.getUsername() : user.getEmail(), ex);
                        return Maybe.error(new TechnicalManagementException("An error has occurred while searching user from the remote HTTP identity provider", ex));
                    });
        } catch (Exception ex) {
            LOGGER.error("An error has occurred while searching the user {}", user.getUsername() != null ? user.getUsername() : user.getEmail(), ex);
            return Maybe.error(new TechnicalManagementException("An error has occurred while searching the user", ex));
        }
    }


    private User convert(String username, Map userAttributes) {
        // remove password key if present to avoid mapper transformation
        // we don't want to store this value
        userAttributes.remove("password");
        final Map mappedUserAttributes = applyUserMapping(new SimpleAuthenticationContext(), userAttributes);

        final String identifierAttribute = configuration.getUsersResource().getIdentifierAttribute();
        final String usernameAttribute = configuration.getUsersResource().getUsernameAttribute();
        // Search the id in mapped attributes first, if missing fallback to attributes before the mapping
        final String id = ofNullable(mappedUserAttributes.get(identifierAttribute))
                .or(() -> ofNullable(userAttributes.get(identifierAttribute)))
                .map(String::valueOf).orElse(null);

        // Search the username in mapped attributes first, if missing fallback to attributes before the mapping
        // if both values are null, use id
        final String usernameValue = (username != null) ? username :
                (mappedUserAttributes.get(usernameAttribute) != null) ? String.valueOf(mappedUserAttributes.get(usernameAttribute)) :
                        (userAttributes.get(usernameAttribute) != null) ? String.valueOf(userAttributes.get(usernameAttribute)) : id;

        DefaultUser user = new DefaultUser(usernameValue);
        // set external id
        user.setId(id);
        // remove sensitive value if any
        mappedUserAttributes.remove(identifierAttribute);
        mappedUserAttributes.remove(usernameAttribute);
        Map claims = new HashMap<>();
        mappedUserAttributes.forEach((k, v) -> claims.put(k, v));
        user.setAdditionalInformation(claims);

        return user;
    }

    private Map applyUserMapping(AuthenticationContext authContext, Map attributes) {
        if (!this.configuration.getUsersResource().isApplyUserMapper()) {
            return attributes;
        }
        return this.mapper.apply(authContext, attributes);
    }

    private Single> processRequest(TemplateEngine templateEngine,
                                                        String httpURI,
                                                        HttpMethod httpMethod,
                                                        List httpHeaders,
                                                        String httpBody) {
        // prepare request
        final String evaluatedHttpURI = templateEngine.getValue(httpURI, String.class);
        final HttpRequest httpRequest = client.requestAbs(httpMethod, evaluatedHttpURI);

        // set headers
        if (httpHeaders != null) {
            httpHeaders.forEach(header -> {
                String extValue = templateEngine.getValue(header.getValue(), String.class);
                httpRequest.putHeader(header.getName(), extValue);
            });
        }

        // set body
        Single> responseHandler;
        if (httpBody != null && !httpBody.isEmpty()) {
            String bodyRequest = templateEngine.getValue(httpBody, String.class);
            if (!httpRequest.headers().contains(HttpHeaders.CONTENT_TYPE)) {
                httpRequest.putHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(bodyRequest.length()));
                responseHandler = httpRequest.rxSendBuffer(Buffer.buffer(bodyRequest));
            } else {
                String contentTypeHeader = httpRequest.headers().get(HttpHeaders.CONTENT_TYPE);
                switch (contentTypeHeader) {
                    case(MediaType.APPLICATION_JSON):
                        responseHandler = httpRequest.rxSendJsonObject(new JsonObject(bodyRequest));
                        break;
                    case(MediaType.APPLICATION_FORM_URLENCODED):
                        Map queryParameters = format(bodyRequest);
                        MultiMap multiMap = MultiMap.caseInsensitiveMultiMap();
                        multiMap.setAll(queryParameters);
                        responseHandler = httpRequest.rxSendForm(multiMap);
                        break;
                    default:
                        httpRequest.putHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(bodyRequest.length()));
                        responseHandler = httpRequest.rxSendBuffer(Buffer.buffer(bodyRequest));
                }
            }
        } else {
            responseHandler = httpRequest.rxSend();
        }
        return responseHandler;
    }

    private Map processResponse(TemplateEngine templateEngine, List errorConditions, HttpResponse httpResponse) throws Exception {
        String responseBody =  httpResponse.bodyAsString();
        templateEngine.getTemplateContext().setVariable(USER_API_RESPONSE_CONTEXT_KEY, new HttpIdentityProviderResponse(httpResponse, responseBody));

        // process response
        Exception lastException = null;
        if (errorConditions != null) {
            Iterator iter = errorConditions.iterator();
            while (iter.hasNext() && lastException == null) {
                HttpResponseErrorCondition errorCondition = iter.next();
                if (templateEngine.getValue(errorCondition.getValue(), Boolean.class)) {
                    Class clazz = (Class) Class.forName(errorCondition.getException());
                    if (errorCondition.getMessage() != null) {
                        String errorMessage = templateEngine.getValue(errorCondition.getMessage(), String.class);
                        Constructor constructor = clazz.getConstructor(String.class);
                        lastException = clazz.cast(constructor.newInstance(new Object[]{errorMessage}));
                    } else {
                        lastException = clazz.newInstance();
                    }
                }
            }
        }

        // if remote API call failed, throw exception
        if (lastException != null) {
            throw lastException;
        }
        if (responseBody == null) {
            return Collections.emptyMap();
        }
        return responseBody.startsWith("[") ?
                new JsonArray(responseBody).getJsonObject(0).getMap() : new JsonObject(responseBody).getMap();
    }

    private static Map format(String query) {
        Map queryPairs = new LinkedHashMap();
        String[] pairs = query.split("&");
        for (String pair : pairs) {
            int idx = pair.indexOf("=");
            queryPairs.put(pair.substring(0, idx), pair.substring(idx + 1));
        }
        return queryPairs;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy