View Javadoc
1   /*
2    * Copyright 2008 Web Cohesion
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.security.oauth2.config.xml;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.List;
22  
23  import org.springframework.beans.factory.FactoryBean;
24  import org.springframework.beans.factory.config.TypedStringValue;
25  import org.springframework.beans.factory.support.BeanDefinitionBuilder;
26  import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
27  import org.springframework.beans.factory.xml.ParserContext;
28  import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
29  import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
30  import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
31  import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitResourceDetails;
32  import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
33  import org.springframework.security.oauth2.common.AuthenticationScheme;
34  import org.springframework.security.oauth2.common.OAuth2AccessToken;
35  import org.springframework.util.StringUtils;
36  import org.w3c.dom.Element;
37  
38  /**
39   * @author Ryan Heaton
40   */
41  public class ResourceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
42  
43  	@Override
44  	protected Class<?> getBeanClass(Element element) {
45  		String type = element.getAttribute("type");
46  		if ("authorization_code".equals(type)) {
47  			return AuthorizationCodeResourceDetails.class;
48  		}
49  		if ("implicit".equals(type)) {
50  			return ImplicitResourceDetails.class;
51  		}
52  		if ("client_credentials".equals(type)) {
53  			return ClientCredentialsResourceDetails.class;
54  		}
55  		if ("password".equals(type)) {
56  			return ResourceOwnerPasswordResourceDetails.class;
57  		}
58  		return BaseOAuth2ProtectedResourceDetails.class;
59  	}
60  
61  	@Override
62  	protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
63  		String id = element.getAttribute("id");
64  		if (!StringUtils.hasText(id)) {
65  			parserContext.getReaderContext().error("An id must be supplied on a resource element.", element);
66  		}
67  		builder.addPropertyValue("id", id);
68  
69  		String type = element.getAttribute("type");
70  		if (!StringUtils.hasText(type)) {
71  			type = "client_credentials";
72  		}
73  		builder.addPropertyValue("grantType", type);
74  
75  		String accessTokenUri = element.getAttribute("access-token-uri");
76  		if (!StringUtils.hasText(accessTokenUri) && !"implicit".equals(type)) {
77  			parserContext.getReaderContext()
78  					.error("An accessTokenUri must be supplied on a resource element of type " + type, element);
79  		}
80  		builder.addPropertyValue("accessTokenUri", accessTokenUri);
81  
82  		String clientId = element.getAttribute("client-id");
83  		if (!StringUtils.hasText(clientId)) {
84  			parserContext.getReaderContext().error("An clientId must be supplied on a resource element", element);
85  		}
86  		builder.addPropertyValue("clientId", clientId);
87  
88  		String clientSecret = element.getAttribute("client-secret");
89  		if (StringUtils.hasText(clientSecret)) {
90  			builder.addPropertyValue("clientSecret", clientSecret);
91  		}
92  
93  		String clientAuthenticationScheme = element.getAttribute("client-authentication-scheme");
94  		if (StringUtils.hasText(clientAuthenticationScheme)) {
95  			builder.addPropertyValue("clientAuthenticationScheme", clientAuthenticationScheme);
96  		}
97  
98  		String userAuthorizationUri = element.getAttribute("user-authorization-uri");
99  		if (StringUtils.hasText(userAuthorizationUri)) {
100 			if (needsUserAuthorizationUri(type)) {
101 				builder.addPropertyValue("userAuthorizationUri", userAuthorizationUri);
102 			} else {
103 				parserContext.getReaderContext().error("The " + type + " grant type does not accept an authorization URI", element);
104 			}
105 		} else {
106 			if (needsUserAuthorizationUri(type)) {
107 				parserContext.getReaderContext().error("An authorization URI must be supplied for a resource of type " + type, element);
108 			}
109 		}
110 
111 		String preEstablishedRedirectUri = element.getAttribute("pre-established-redirect-uri");
112 		if (StringUtils.hasText(preEstablishedRedirectUri)) {
113 			builder.addPropertyValue("preEstablishedRedirectUri", preEstablishedRedirectUri);
114 		}
115 
116 		String requireImmediateAuthorization = element.getAttribute("require-immediate-authorization");
117 		if (StringUtils.hasText(requireImmediateAuthorization)) {
118 			builder.addPropertyValue("requireImmediateAuthorization", requireImmediateAuthorization);
119 		}
120 
121 		String useCurrentUri = element.getAttribute("use-current-uri");
122 		if (StringUtils.hasText(useCurrentUri)) {
123 			builder.addPropertyValue("useCurrentUri", useCurrentUri);
124 		}
125 
126 		String scope = element.getAttribute("scope");
127 		if (StringUtils.hasText(scope)) {
128 			BeanDefinitionBuilder scopesBuilder = BeanDefinitionBuilder
129 					.genericBeanDefinition(StringListFactoryBean.class);
130 			scopesBuilder.addConstructorArgValue(new TypedStringValue(scope));
131 			builder.addPropertyValue("scope", scopesBuilder.getBeanDefinition());
132 		}
133 
134 		AuthenticationScheme btm = AuthenticationScheme.header;
135 		String bearerTokenMethod = element.getAttribute("authentication-scheme");
136 		if (StringUtils.hasText(bearerTokenMethod)) {
137 			btm = AuthenticationScheme.valueOf(bearerTokenMethod);
138 		}
139 		builder.addPropertyValue("authenticationScheme", btm);
140 
141 		String bearerTokenName = element.getAttribute("token-name");
142 		if (!StringUtils.hasText(bearerTokenName)) {
143 			bearerTokenName = OAuth2AccessToken.ACCESS_TOKEN;
144 		}
145 		builder.addPropertyValue("tokenName", bearerTokenName);
146 
147 		if (type.equals("password")) {
148 			String[] attributeNames = {"username", "password"};
149 			for (String attributeName : attributeNames) {
150 				String attribute = element.getAttribute(attributeName);
151 				if (StringUtils.hasText(attribute)) {
152 					builder.addPropertyValue(attributeName, attribute);
153 				} else {
154 					parserContext.getReaderContext().error("A " + attributeName + " must be supplied on a resource element of type " + type, element);
155 				}
156 			}
157 		}
158 	}
159 
160 	/**
161 	 * Convenience factory bean for enabling comma-separated lists to be specified either as literals or externalized as
162 	 * expressions or placeholders. N.B. this would not be necessary if Spring used its ConversionService by default
163 	 * (users have to register one).
164 	 */
165 	public static class StringListFactoryBean implements FactoryBean<List<String>> {
166 
167 		private final String commaSeparatedList;
168 
169 		public StringListFactoryBean(String commaSeparatedList) {
170 			this.commaSeparatedList = commaSeparatedList;
171 		}
172 
173 		public List<String> getObject() throws Exception {
174 			return new ArrayList<String>(Arrays.asList(StringUtils.commaDelimitedListToStringArray(commaSeparatedList)));
175 		}
176 
177 		public Class<?> getObjectType() {
178 			return List.class;
179 		}
180 
181 		public boolean isSingleton() {
182 			return true;
183 		}
184 
185 	}
186 
187 	private boolean needsUserAuthorizationUri(String type) {
188 		return type.equals("authorization_code") || type.equals("implicit");
189 	}
190 
191 }