001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.autoconfigure.security.oauth2.client;
018
019import java.util.HashMap;
020import java.util.Map;
021
022import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Provider;
023import org.springframework.boot.context.properties.PropertyMapper;
024import org.springframework.boot.convert.ApplicationConversionService;
025import org.springframework.core.convert.ConversionException;
026import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
027import org.springframework.security.oauth2.client.registration.ClientRegistration;
028import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder;
029import org.springframework.security.oauth2.client.registration.ClientRegistrations;
030import org.springframework.security.oauth2.core.AuthenticationMethod;
031import org.springframework.security.oauth2.core.AuthorizationGrantType;
032import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
033import org.springframework.util.StringUtils;
034
035/**
036 * Adapter class to convert {@link OAuth2ClientProperties} to a
037 * {@link ClientRegistration}.
038 *
039 * @author Phillip Webb
040 * @author Thiago Hirata
041 * @author Madhura Bhave
042 * @author MyeongHyeon Lee
043 * @since 2.1.0
044 */
045public final class OAuth2ClientPropertiesRegistrationAdapter {
046
047        private OAuth2ClientPropertiesRegistrationAdapter() {
048        }
049
050        public static Map<String, ClientRegistration> getClientRegistrations(
051                        OAuth2ClientProperties properties) {
052                Map<String, ClientRegistration> clientRegistrations = new HashMap<>();
053                properties.getRegistration().forEach((key, value) -> clientRegistrations.put(key,
054                                getClientRegistration(key, value, properties.getProvider())));
055                return clientRegistrations;
056        }
057
058        private static ClientRegistration getClientRegistration(String registrationId,
059                        OAuth2ClientProperties.Registration properties,
060                        Map<String, Provider> providers) {
061                Builder builder = getBuilderFromIssuerIfPossible(registrationId,
062                                properties.getProvider(), providers);
063                if (builder == null) {
064                        builder = getBuilder(registrationId, properties.getProvider(), providers);
065                }
066                PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
067                map.from(properties::getClientId).to(builder::clientId);
068                map.from(properties::getClientSecret).to(builder::clientSecret);
069                map.from(properties::getClientAuthenticationMethod)
070                                .as(ClientAuthenticationMethod::new)
071                                .to(builder::clientAuthenticationMethod);
072                map.from(properties::getAuthorizationGrantType).as(AuthorizationGrantType::new)
073                                .to(builder::authorizationGrantType);
074                map.from(properties::getRedirectUri).to(builder::redirectUriTemplate);
075                map.from(properties::getScope).as((scope) -> StringUtils.toStringArray(scope))
076                                .to(builder::scope);
077                map.from(properties::getClientName).to(builder::clientName);
078                return builder.build();
079        }
080
081        private static Builder getBuilderFromIssuerIfPossible(String registrationId,
082                        String configuredProviderId, Map<String, Provider> providers) {
083                String providerId = (configuredProviderId != null) ? configuredProviderId
084                                : registrationId;
085                if (providers.containsKey(providerId)) {
086                        Provider provider = providers.get(providerId);
087                        String issuer = provider.getIssuerUri();
088                        if (issuer != null) {
089                                String cleanedIssuer = cleanIssuerPath(issuer);
090                                Builder builder = ClientRegistrations
091                                                .fromOidcIssuerLocation(cleanedIssuer)
092                                                .registrationId(registrationId);
093                                return getBuilder(builder, provider);
094                        }
095                }
096                return null;
097        }
098
099        private static String cleanIssuerPath(String issuer) {
100                if (issuer.endsWith("/")) {
101                        return issuer.substring(0, issuer.length() - 1);
102                }
103                return issuer;
104        }
105
106        private static Builder getBuilder(String registrationId, String configuredProviderId,
107                        Map<String, Provider> providers) {
108                String providerId = (configuredProviderId != null) ? configuredProviderId
109                                : registrationId;
110                CommonOAuth2Provider provider = getCommonProvider(providerId);
111                if (provider == null && !providers.containsKey(providerId)) {
112                        throw new IllegalStateException(
113                                        getErrorMessage(configuredProviderId, registrationId));
114                }
115                Builder builder = (provider != null) ? provider.getBuilder(registrationId)
116                                : ClientRegistration.withRegistrationId(registrationId);
117                if (providers.containsKey(providerId)) {
118                        return getBuilder(builder, providers.get(providerId));
119                }
120                return builder;
121        }
122
123        private static String getErrorMessage(String configuredProviderId,
124                        String registrationId) {
125                return ((configuredProviderId != null)
126                                ? "Unknown provider ID '" + configuredProviderId + "'"
127                                : "Provider ID must be specified for client registration '"
128                                                + registrationId + "'");
129        }
130
131        private static Builder getBuilder(Builder builder, Provider provider) {
132                PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
133                map.from(provider::getAuthorizationUri).to(builder::authorizationUri);
134                map.from(provider::getTokenUri).to(builder::tokenUri);
135                map.from(provider::getUserInfoUri).to(builder::userInfoUri);
136                map.from(provider::getUserInfoAuthenticationMethod).as(AuthenticationMethod::new)
137                                .to(builder::userInfoAuthenticationMethod);
138                map.from(provider::getJwkSetUri).to(builder::jwkSetUri);
139                map.from(provider::getUserNameAttribute).to(builder::userNameAttributeName);
140                return builder;
141        }
142
143        private static CommonOAuth2Provider getCommonProvider(String providerId) {
144                try {
145                        return ApplicationConversionService.getSharedInstance().convert(providerId,
146                                        CommonOAuth2Provider.class);
147                }
148                catch (ConversionException ex) {
149                        return null;
150                }
151        }
152
153}