Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'release/1.2.0'

Change-Id: I4e3513a4b76a67c4e08309536141ba21785d3282
  • Loading branch information...
commit 8d0ae84a800f976c07c0b37f6cddd69229396ce7 2 parents c430116 + 817b38b
@joeldsa joeldsa authored
Showing with 6,073 additions and 8,113 deletions.
  1. +2 −2 common/pom.xml
  2. +19 −2 common/src/main/java/org/cloudfoundry/identity/uaa/authentication/AuthzAuthenticationFilter.java
  3. +2 −2 common/src/main/java/org/cloudfoundry/identity/uaa/authentication/AuthzAuthenticationRequest.java
  4. +64 −26 common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java
  5. +23 −13 common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java
  6. +5 −5 common/src/main/java/org/cloudfoundry/identity/uaa/config/HandlerAdapterFactoryBean.java
  7. +4 −0 common/src/main/java/org/cloudfoundry/identity/uaa/error/UaaException.java
  8. +0 −4 common/src/main/java/org/cloudfoundry/identity/uaa/integration/UaaTestAccounts.java
  9. +5 −2 common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java
  10. +20 −10 common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpoints.java
  11. +60 −0 common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientInfoEndpoint.java
  12. +6 −4 ...ailException.java → common/src/main/java/org/cloudfoundry/identity/uaa/oauth/NoSuchTokenException.java
  13. +19 −21 common/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenAdminEndpoints.java
  14. +3 −2 common/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordCheckEndpoint.java
  15. +3 −1 common/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordScoreCalculator.java
  16. +4 −2 common/src/main/java/org/cloudfoundry/identity/uaa/password/ZxcvbnPasswordScoreCalculator.java
  17. +62 −0 common/src/main/java/org/cloudfoundry/identity/uaa/rest/SimpleMessage.java
  18. +16 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/AttributeNameMapper.java
  19. +2 −2 ...ava/org/cloudfoundry/identity/uaa/scim/{InvalidUserException.java → InvalidScimResourceException.java}
  20. +106 −11 common/src/main/java/org/cloudfoundry/identity/uaa/scim/JdbcPagingList.java
  21. +45 −214 common/src/main/java/org/cloudfoundry/identity/uaa/scim/JdbcScimUserProvisioning.java
  22. +5 −5 common/src/main/java/org/cloudfoundry/identity/uaa/scim/RemoteScimUserProvisioning.java
  23. +83 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java
  24. +54 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java
  25. +2 −2 ...dfoundry/identity/uaa/scim/{UserAlreadyExistsException.java → ScimResourceAlreadyExistsException.java}
  26. +2 −2 ...a/org/cloudfoundry/identity/uaa/scim/{UserConflictException.java → ScimResourceConflictException.java}
  27. +2 −2 ...a/org/cloudfoundry/identity/uaa/scim/{UserNotFoundException.java → ScimResourceNotFoundException.java}
  28. +138 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimSearchQueryConverter.java
  29. +27 −106 common/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java
  30. +52 −19 common/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserBootstrap.java
  31. +53 −47 common/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserEndpoints.java
  32. +5 −5 common/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserProvisioning.java
  33. +34 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/SearchQueryConverter.java
  34. +41 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/SearchResultsFactory.java
  35. +50 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/SimpleAttributeNameMapper.java
  36. +40 −31 ...n/java/org/cloudfoundry/identity/uaa/scim/{GroupsUsersEndpoints.java → UserIdConversionEndpoints.java}
  37. +299 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/groups/JdbcScimGroupMembershipManager.java
  38. +177 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/groups/JdbcScimGroupProvisioning.java
  39. +11 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/groups/MemberAlreadyExistsException.java
  40. +11 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/groups/MemberNotFoundException.java
  41. +57 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/groups/ScimGroup.java
  42. +168 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/groups/ScimGroupBootstrap.java
  43. +211 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/groups/ScimGroupEndpoints.java
  44. +81 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/groups/ScimGroupMember.java
  45. +94 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/groups/ScimGroupMembershipManager.java
  46. +24 −0 common/src/main/java/org/cloudfoundry/identity/uaa/scim/groups/ScimGroupProvisioning.java
  47. +18 −0 common/src/main/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessor.java
  48. +7 −2 common/src/main/java/org/cloudfoundry/identity/uaa/security/SecurityContextAccessor.java
  49. +12 −113 common/src/main/java/org/cloudfoundry/identity/uaa/social/SocialClientAuthenticationFilter.java
  50. +171 −0 common/src/main/java/org/cloudfoundry/identity/uaa/social/SocialClientUserDetailsSource.java
  51. +10 −0 common/src/main/java/org/cloudfoundry/identity/uaa/test/TestUtils.java
  52. +31 −0 common/src/main/java/org/cloudfoundry/identity/uaa/varz/HealthzEndpoint.java
  53. +0 −6 common/src/main/java/org/cloudfoundry/identity/uaa/varz/VarzEndpoint.java
  54. +3 −1 common/src/main/resources/org/cloudfoundry/identity/uaa/schema-drop-hsqldb.sql
  55. +3 −1 common/src/main/resources/org/cloudfoundry/identity/uaa/schema-drop-postgresql.sql
  56. +18 −0 common/src/main/resources/org/cloudfoundry/identity/uaa/schema-hsqldb.sql
  57. +18 −0 common/src/main/resources/org/cloudfoundry/identity/uaa/schema-postgresql.sql
  58. +4 −0 common/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationTestFactory.java
  59. +136 −0 common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManagerTests.java
  60. +23 −58 common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsTests.java
  61. +56 −0 common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientInfoEndpointTests.java
  62. +16 −17 common/src/test/java/org/cloudfoundry/identity/uaa/oauth/TokenAdminEndpointsTests.java
  63. +2 −37 common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestFactoryTests.java
  64. +2 −2 common/src/test/java/org/cloudfoundry/identity/uaa/password/PasswordCheckEndpointTests.java
  65. +44 −0 common/src/test/java/org/cloudfoundry/identity/uaa/rest/MessageTests.java
  66. +42 −0 common/src/test/java/org/cloudfoundry/identity/uaa/scim/JdbcPagingListTests.java
  67. +29 −31 common/src/test/java/org/cloudfoundry/identity/uaa/scim/JdbcScimUserProvisioningTests.java
  68. +24 −0 common/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimCoreTests.java
  69. +59 −0 common/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimSearchQueryConverterTests.java
  70. +26 −7 common/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserBootstrapTests.java
  71. +140 −9 common/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserEndpointsTests.java
  72. +9 −22 .../cloudfoundry/identity/uaa/scim/{GroupsUsersEndpointsTests.java → UserIdConversionEndpointsTests.java}
  73. +307 −0 common/src/test/java/org/cloudfoundry/identity/uaa/scim/groups/JdbcScimGroupMembershipManagerTests.java
  74. +189 −0 common/src/test/java/org/cloudfoundry/identity/uaa/scim/groups/JdbcScimGroupProvisioningTests.java
  75. +115 −0 common/src/test/java/org/cloudfoundry/identity/uaa/scim/groups/ScimGroupBootstrapTests.java
  76. +241 −0 common/src/test/java/org/cloudfoundry/identity/uaa/scim/groups/ScimGroupEndpointsTests.java
  77. +41 −0 common/src/test/java/org/cloudfoundry/identity/uaa/scim/groups/ScimGroupMemberTests.java
  78. +58 −0 common/src/test/java/org/cloudfoundry/identity/uaa/security/StubSecurityContextAccessor.java
  79. +10 −0 common/src/test/java/org/cloudfoundry/identity/uaa/security/web/UaaRequestMatcherTests.java
  80. +7 −17 common/src/test/java/org/cloudfoundry/identity/uaa/social/OAuth2ClientAuthenticationFilterTests.java
  81. +5 −14 common/src/test/java/org/cloudfoundry/identity/uaa/social/OAuthClientAuthenticationFilterTests.java
  82. +28 −0 common/src/test/java/org/cloudfoundry/identity/uaa/varz/HealthzEndpointTests.java
  83. +0 −5 common/src/test/java/org/cloudfoundry/identity/uaa/varz/VarzEndpointTests.java
  84. +64 −70 docs/UAA-APIs.rst
  85. +20 −14 docs/UAA-Security.md
  86. +1 −0  gem/Rakefile
  87. +80 −0 gem/bin/completion-helper
  88. +1 −3 gem/bin/uaac
  89. +34 −0 gem/bin/uaac-completion.sh
  90. +1 −1  gem/cf-uaa-client.gemspec
  91. +76 −19 gem/lib/cli/base.rb
  92. +31 −41 gem/lib/cli/client_reg.rb
  93. +55 −58 gem/lib/cli/common.rb
  94. +23 −17 gem/lib/cli/config.rb
  95. +29 −16 gem/lib/cli/group.rb
  96. +11 −14 gem/lib/cli/info.rb
  97. +4 −4 gem/lib/cli/runner.rb
  98. +40 −37 gem/lib/cli/token.rb
  99. +64 −32 gem/lib/cli/user.rb
  100. +2 −2 gem/lib/uaa/client_reg.rb
  101. +37 −43 gem/lib/uaa/http.rb
  102. +0 −2  gem/lib/uaa/misc.rb
  103. +18 −8 gem/lib/uaa/token_issuer.rb
  104. +82 −37 gem/lib/uaa/user_account.rb
  105. +4 −3 gem/lib/uaa/util.rb
  106. +1 −1  gem/lib/uaa/version.rb
  107. +122 −45 gem/spec/cli_spec.rb
  108. +7 −14 gem/spec/http_spec.rb
  109. +25 −25 gem/spec/integration_spec.rb
  110. +16 −97 gem/spec/misc_spec.rb
  111. +131 −89 gem/spec/stub_scim.rb
  112. +240 −181 gem/spec/stub_uaa.rb
  113. +44 −123 gem/spec/token_issuer_spec.rb
  114. +28 −4 gem/spec/user_account_spec.rb
  115. +3 −3 pom.xml
  116. +1 −1  samples/api/pom.xml
  117. +40 −0 samples/api/src/main/java/org/cloudfoundry/identity/api/web/CorsFilter.java
  118. +14 −14 samples/api/src/main/webapp/WEB-INF/web.xml
  119. +2 −0  samples/app/.gitignore
  120. +1 −1  samples/app/pom.xml
  121. +25 −8 samples/app/src/main/java/org/cloudfoundry/identity/app/web/HomeController.java
  122. +11 −7 samples/app/src/main/webapp/WEB-INF/spring-servlet.xml
  123. +6 −2 samples/app/src/main/webapp/browse.jsp
  124. +1 −1  samples/app/src/main/webapp/resources/js/libs/jso.js
  125. +1 −1  samples/login/pom.xml
  126. +0 −20 samples/login/src/main/java/org/cloudfoundry/identity/uaa/login/RemoteUaaController.java
  127. +8 −160 samples/login/src/main/webapp/WEB-INF/jsp/access_confirmation.jsp
  128. +13 −149 samples/login/src/main/webapp/WEB-INF/jsp/home.jsp
  129. +10 −182 samples/login/src/main/webapp/WEB-INF/jsp/login.jsp
  130. BIN  samples/login/src/main/webapp/favicon.ico
  131. BIN  samples/login/src/main/webapp/resources/images/404.jpg
  132. BIN  samples/login/src/main/webapp/resources/images/beta.png
  133. BIN  samples/login/src/main/webapp/resources/images/bg_stripy.png
  134. BIN  samples/login/src/main/webapp/resources/images/bullet.png
  135. BIN  samples/login/src/main/webapp/resources/images/button_close.png
  136. BIN  samples/login/src/main/webapp/resources/images/button_regenerate.png
  137. BIN  samples/login/src/main/webapp/resources/images/callout_spotlight.png
  138. BIN  samples/login/src/main/webapp/resources/images/cloud_foundry_logo.png
  139. BIN  samples/login/src/main/webapp/resources/images/dots.png
  140. BIN  samples/login/src/main/webapp/resources/images/form_field.png
  141. BIN  samples/login/src/main/webapp/resources/images/icon_document.png
  142. BIN  samples/login/src/main/webapp/resources/images/icon_download.png
  143. BIN  samples/login/src/main/webapp/resources/images/icon_follow.png
  144. BIN  samples/login/src/main/webapp/resources/images/icon_forums.png
  145. BIN  samples/login/src/main/webapp/resources/images/icon_magnify.png
  146. BIN  samples/login/src/main/webapp/resources/images/icon_rss.png
  147. BIN  samples/login/src/main/webapp/resources/images/icon_slides.png
  148. BIN  samples/login/src/main/webapp/resources/images/icon_speech_bubble.png
  149. BIN  samples/login/src/main/webapp/resources/images/icon_video.png
  150. BIN  samples/login/src/main/webapp/resources/images/logo-small.png
  151. BIN  samples/login/src/main/webapp/resources/images/logo_header_cloudfoundry.png
  152. BIN  samples/login/src/main/webapp/resources/images/page_header.png
  153. BIN  samples/login/src/main/webapp/resources/images/social_icons.png
  154. BIN  samples/login/src/main/webapp/resources/images/super_nav_separator.png
  155. BIN  samples/login/src/main/webapp/resources/images/vmware.png
  156. +0 −81 samples/login/src/main/webapp/resources/stylesheets/PIE.htc
  157. +0 −673 samples/login/src/main/webapp/resources/stylesheets/core.css
  158. +0 −36 samples/login/src/main/webapp/resources/stylesheets/hosted.css
  159. +0 −99 samples/login/src/main/webapp/resources/stylesheets/ie.css
  160. +0 −127 samples/login/src/main/webapp/resources/stylesheets/ie7.css
  161. +0 −384 samples/login/src/main/webapp/resources/stylesheets/master-cf.css
  162. +0 −2,533 samples/login/src/main/webapp/resources/stylesheets/master.css
  163. +0 −105 samples/login/src/main/webapp/resources/stylesheets/micro.css
  164. +0 −26 samples/login/src/main/webapp/resources/stylesheets/passwd.css
  165. +0 −49 samples/login/src/main/webapp/resources/stylesheets/print.css
  166. +1 −1  samples/pom.xml
  167. +0 −1  uaa/.springBeans
  168. +2 −15 uaa/pom.xml
  169. +0 −70 uaa/src/main/java/org/cloudfoundry/identity/uaa/scim/job/AdminUsersTasklet.java
  170. +0 −96 uaa/src/main/java/org/cloudfoundry/identity/uaa/scim/job/CloudControllerLastModifiedFilterProcessor.java
  171. +0 −113 uaa/src/main/java/org/cloudfoundry/identity/uaa/scim/job/CloudControllerUserItemProcessor.java
  172. +0 −60 uaa/src/main/java/org/cloudfoundry/identity/uaa/scim/job/GenericSqlTasklet.java
  173. +0 −28 uaa/src/main/java/org/cloudfoundry/identity/uaa/scim/job/MapItemSqlParameterSourceProvider.java
  174. +0 −102 uaa/src/main/java/org/cloudfoundry/identity/uaa/scim/job/UaaUserItemProcessor.java
  175. +0 −210 uaa/src/main/resources/META-INF/spring/batch/jobs/jobs.xml
  176. +0 −9 uaa/src/main/resources/batch-default.properties
  177. 0  uaa/src/main/resources/business-schema-hsqldb.sql
  178. +0 −25 uaa/src/main/resources/messages.properties
  179. +18 −0 uaa/src/main/resources/org/cloudfoundry/identity/uaa/schema-cloudfoundry.sql
  180. +3 −1 uaa/src/main/resources/org/cloudfoundry/identity/uaa/schema-drop-cloudfoundry.sql
  181. +3 −16 uaa/src/main/resources/uaa.yml
  182. +0 −49 uaa/src/main/webapp/WEB-INF/applicationContext.xml
  183. +0 −90 uaa/src/main/webapp/WEB-INF/batch-servlet.xml
  184. +1 −1  uaa/src/main/webapp/WEB-INF/jsp/access_confirmation.jsp
  185. +31 −13 uaa/src/main/webapp/WEB-INF/spring-servlet.xml
  186. +29 −0 uaa/src/main/webapp/WEB-INF/spring/audit.xml
  187. +2 −2 uaa/src/main/webapp/WEB-INF/spring/login-server-security.xml
  188. +13 −0 uaa/src/main/webapp/WEB-INF/spring/resource-endpoints.xml
  189. +110 −20 uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml
  190. +17 −9 uaa/src/main/webapp/WEB-INF/varz-servlet.xml
  191. +4 −41 uaa/src/main/webapp/WEB-INF/web.xml
  192. +0 −142 uaa/src/main/webapp/WEB-INF/web/manager/jobs/html/execution.ftl
  193. +1 −3 uaa/src/test/java/org/cloudfoundry/identity/uaa/AdhocTestSuite.java
  194. +0 −11 uaa/src/test/java/org/cloudfoundry/identity/uaa/BootstrapTests.java
  195. +3 −3 uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java
  196. +88 −0 uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientInfoEndpointIntegrationTests.java
  197. +109 −0 uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/FormLoginIntegrationTests.java
  198. +5 −26 ...y/identity/uaa/integration/{BatchEndpointIntegrationTests.java → HealthzEndpointIntegrationTests.java}
  199. +13 −5 uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java
  200. +17 −0 uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordCheckEndpointIntegrationTests.java
  201. +64 −13 uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimUserEndpointsIntegrationTests.java
  202. +5 −10 uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/TokenAdminEndpointsIntegrationTests.java
  203. +3 −3 uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/VmcScimUserEndpointIntegrationTests.java
  204. +9 −30 ...tion/{VmcGroupsUsersEndpointIntegrationTests.java → VmcUserIdTranslationEndpointIntegrationTests.java}
  205. +0 −97 uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/jobs/AbstractJobIntegrationTests.java
  206. +0 −60 uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/jobs/BackwardMigrationJobIntegrationTests.java
  207. +0 −64 uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/jobs/UserMigrationJobIntegrationTests.java
  208. +0 −84 uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/jobs/UserSyncJobIntegrationTests.java
  209. +0 −22 uaa/src/test/resources/org/cloudfoundry/identity/uaa/cloud-controller-schema-hsqldb.sql
  210. +0 −22 uaa/src/test/resources/org/cloudfoundry/identity/uaa/cloud-controller-schema-postgresql.sql
View
4 common/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.cloudfoundry.identity</groupId>
<artifactId>cloudfoundry-identity-parent</artifactId>
- <version>1.2.0.BUILD-SNAPSHOT</version>
+ <version>1.2.0</version>
<relativePath>..</relativePath>
</parent>
@@ -247,7 +247,7 @@
<dependency>
<groupId>eu.tekul</groupId>
<artifactId>szxcvbn_2.8.2</artifactId>
- <version>0.1</version>
+ <version>0.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
View
21 ...src/main/java/org/cloudfoundry/identity/uaa/authentication/AuthzAuthenticationFilter.java
@@ -15,8 +15,10 @@
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -31,6 +33,7 @@
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
+import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
@@ -66,6 +69,20 @@
private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
+ private Set<String> methods = Collections.singleton(HttpMethod.POST.toString());
+
+ /**
+ * The filter fails on requests that don't have one of these HTTP methods.
+ *
+ * @param methods the methods to set (defaults to POST)
+ */
+ public void setMethods(Set<String> methods) {
+ this.methods = new HashSet<String>();
+ for (String method : methods) {
+ this.methods.add(method.toUpperCase());
+ }
+ }
+
/**
* @param authenticationEntryPoint the authenticationEntryPoint to set
*/
@@ -103,8 +120,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
else {
logger.debug("Located credentials in request, with keys: " + loginInfo.keySet());
try {
- if (!"POST".equals(req.getMethod().toUpperCase())) {
- throw new BadCredentialsException("Credentials must be sent via POST");
+ if (methods!=null && !methods.contains(req.getMethod().toUpperCase())) {
+ throw new BadCredentialsException("Credentials must be sent by (one of methods): " + methods);
}
Authentication result = authenticationManager.authenticate(new AuthzAuthenticationRequest(loginInfo,
new UaaAuthenticationDetails(req)));
View
4 ...rc/main/java/org/cloudfoundry/identity/uaa/authentication/AuthzAuthenticationRequest.java
@@ -41,8 +41,8 @@ public AuthzAuthenticationRequest(Map<String,String> info, UaaAuthenticationDeta
}
public AuthzAuthenticationRequest(String username, String password, UaaAuthenticationDetails details) {
- Assert.hasText("username", "username cannot be empty");
- Assert.hasText("password", "password cannot be empty");
+ Assert.hasText(username, "username cannot be empty");
+ Assert.hasText(password, "password cannot be empty");
HashMap<String, String> info = new HashMap<String, String>();
info.put("username", username.trim());
info.put("password", password.trim());
View
90 ...java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java
@@ -12,7 +12,10 @@
*/
package org.cloudfoundry.identity.uaa.authentication.manager;
+import java.security.SecureRandom;
+import java.util.List;
import java.util.Locale;
+import java.util.UUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -30,8 +33,10 @@
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.codec.Hex;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
@@ -46,6 +51,11 @@
private final UaaUserDatabase userDatabase;
private ApplicationEventPublisher eventPublisher;
private AccountLoginPolicy accountLoginPolicy = new PermitAllAccountLoginPolicy();
+ /**
+ * Dummy user allows the authentication process for non-existent and locked out users to be as close to
+ * that of normal users as possible to avoid differences in timing.
+ */
+ private final UaaUser dummyUser;
public AuthzAuthenticationManager(UaaUserDatabase cfusers) {
this(cfusers, new BCryptPasswordEncoder());
@@ -54,44 +64,52 @@ public AuthzAuthenticationManager(UaaUserDatabase cfusers) {
public AuthzAuthenticationManager(UaaUserDatabase userDatabase, PasswordEncoder encoder) {
this.userDatabase = userDatabase;
this.encoder = encoder;
+ this.dummyUser = createDummyUser();
}
@Override
public Authentication authenticate(Authentication req) throws AuthenticationException {
+ logger.debug("Processing authentication request for " + req.getName());
+
+ if (req.getCredentials() == null) {
+ throw new BadCredentialsException("No password supplied");
+ }
+
+ UaaUser user;
try {
- logger.debug("Processing authentication request for " + req.getName());
- UaaUser user = userDatabase.retrieveUserByName(req.getName().toLowerCase(Locale.US));
-
- if (!accountLoginPolicy.isAllowed(user, req)) {
- logger.warn("Login policy rejected authentication for " + user.getUsername() + ", " + user.getId()
- + ". Ignoring login request.");
- // TODO: We should perhaps have another audit event type here
- // since this will not be logged as an authentication failure.
- throw new BadCredentialsException("Login policy rejected authentication");
- }
+ user = userDatabase.retrieveUserByName(req.getName().toLowerCase(Locale.US));
+ }
+ catch (UsernameNotFoundException e) {
+ user = dummyUser;
+ }
- if (req.getCredentials() == null) {
- throw new BadCredentialsException("No password supplied");
- }
+ final boolean passwordMatches = encoder.matches((CharSequence) req.getCredentials(), user.getPassword());
- if (encoder.matches((CharSequence) req.getCredentials(), user.getPassword())) {
- logger.debug("Password successfully matched");
- Authentication success = new UaaAuthentication(new UaaPrincipal(user),
- user.getAuthorities(), (UaaAuthenticationDetails) req.getDetails());
- eventPublisher.publishEvent(new UserAuthenticationSuccessEvent(user, success));
+ if (!accountLoginPolicy.isAllowed(user, req)) {
+ logger.warn("Login policy rejected authentication for " + user.getUsername() + ", " + user.getId()
+ + ". Ignoring login request.");
+ // TODO: We should perhaps have another audit event type here
+ // since this will not be logged as an authentication failure.
+ throw new BadCredentialsException("Login policy rejected authentication");
+ }
- return success;
- }
- logger.debug("Password did not match");
- eventPublisher.publishEvent(new UserAuthenticationFailureEvent(user, req));
+ if (passwordMatches) {
+ logger.debug("Password successfully matched");
+ Authentication success = new UaaAuthentication(new UaaPrincipal(user),
+ user.getAuthorities(), (UaaAuthenticationDetails) req.getDetails());
+ eventPublisher.publishEvent(new UserAuthenticationSuccessEvent(user, success));
- throw new BadCredentialsException("Bad credentials");
+ return success;
}
- catch (UsernameNotFoundException e) {
- eventPublisher.publishEvent(new UserNotFoundEvent(req));
+
+ if (user == dummyUser) {
logger.debug("No user named '" + req.getName() + "' was found");
- throw new BadCredentialsException("Bad credentials");
+ eventPublisher.publishEvent(new UserNotFoundEvent(req));
+ } else {
+ logger.debug("Password did not match for user " + req.getName());
+ eventPublisher.publishEvent(new UserAuthenticationFailureEvent(user, req));
}
+ throw new BadCredentialsException("Bad credentials");
}
@Override
@@ -102,4 +120,24 @@ public void setApplicationEventPublisher(ApplicationEventPublisher eventPublishe
public void setAccountLoginPolicy(AccountLoginPolicy accountLoginPolicy) {
this.accountLoginPolicy = accountLoginPolicy;
}
+
+ private UaaUser createDummyUser() {
+ // Create random unguessable password
+ SecureRandom random = new SecureRandom();
+ byte[] passBytes = new byte[16];
+ random.nextBytes(passBytes);
+ String password = encoder.encode(new String(Hex.encode(passBytes)));
+ // Unique ID which isn't in the database
+ final String id = UUID.randomUUID().toString();
+
+ return new UaaUser("dummy_user", password, "dummy_user", "dummy", "dummy") {
+ public final String getId() {
+ return id;
+ }
+
+ public final List<? extends GrantedAuthority> getAuthorities() {
+ throw new IllegalStateException();
+ }
+ };
+ }
}
View
36 ...java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java
@@ -32,7 +32,7 @@
private ApplicationEventPublisher eventPublisher;
private ScimUserBootstrap scimUserBootstrap;
-
+
private UaaUserDatabase userDatabase;
boolean addNewAccounts = false;
@@ -62,18 +62,19 @@ public void setApplicationEventPublisher(ApplicationEventPublisher eventPublishe
public void setScimUserBootstrap(ScimUserBootstrap scimUserBootstrap) {
this.scimUserBootstrap = scimUserBootstrap;
}
-
+
/**
* @param userDatabase the userDatabase to set
*/
public void setUserDatabase(UaaUserDatabase userDatabase) {
this.userDatabase = userDatabase;
}
-
+
@Override
public Authentication authenticate(Authentication request) throws AuthenticationException {
if (!(request instanceof AuthzAuthenticationRequest)) {
+ logger.debug("Cannot process request of type: " + request.getClass().getName());
return null;
}
@@ -88,13 +89,22 @@ public Authentication authenticate(Authentication request) throws Authentication
OAuth2Authentication authentication = (OAuth2Authentication) context.getAuthentication();
if (authentication.isClientOnly()) {
UaaUser user = getUser(req, info);
- if (scimUserBootstrap != null && addNewAccounts) {
- // Register new users automatically
- scimUserBootstrap.addUser(user);
- } else {
- try {
- user = userDatabase.retrieveUserByName(user.getUsername());
- } catch (UsernameNotFoundException e) {
+ try {
+ user = userDatabase.retrieveUserByName(user.getUsername());
+ }
+ catch (UsernameNotFoundException e) {
+ // Not necessarily fatal
+ if (scimUserBootstrap != null && addNewAccounts) {
+ // Register new users automatically
+ scimUserBootstrap.addUser(user);
+ try {
+ user = userDatabase.retrieveUserByName(user.getUsername());
+ }
+ catch (UsernameNotFoundException ex) {
+ throw new BadCredentialsException("Bad credentials");
+ }
+ }
+ else {
throw new BadCredentialsException("Bad credentials");
}
}
@@ -106,17 +116,17 @@ public Authentication authenticate(Authentication request) throws Authentication
}
logger.debug("Did not locate login credentials");
- throw new BadCredentialsException("Bad credentials");
+ return null;
}
protected UaaUser getUser(AuthzAuthenticationRequest req, Map<String, String> info) {
String name = req.getName();
String email = info.get("email");
- if (name==null && email!=null) {
+ if (name == null && email != null) {
name = email;
}
- if (name==null) {
+ if (name == null) {
throw new BadCredentialsException("Cannot determine username from credentials supplied");
}
if (email == null) {
View
10 common/src/main/java/org/cloudfoundry/identity/uaa/config/HandlerAdapterFactoryBean.java
@@ -18,7 +18,7 @@
import javax.servlet.http.HttpServletResponse;
-import org.cloudfoundry.identity.uaa.scim.ScimUser;
+import org.cloudfoundry.identity.uaa.scim.ScimCore;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
@@ -38,7 +38,7 @@
/**
* Factory for a handler adapter that sniffs the results from {@link RequestMapping} method executions and adds an ETag
- * header if the result is a {@link ScimUser}. Inject into application context as anonymous bean.
+ * header if the result is a {@link ScimCore}. Inject into application context as anonymous bean.
*
* @author Dave Syer
*
@@ -81,16 +81,16 @@ public ScimEtagHandlerMethodReturnValueHandler(List<HttpMessageConverter<?>> mes
@Override
public boolean supportsReturnType(MethodParameter returnType) {
- return ScimUser.class.isAssignableFrom(returnType.getMethod().getReturnType());
+ return ScimCore.class.isAssignableFrom(returnType.getMethod().getReturnType());
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException,
HttpMediaTypeNotAcceptableException {
- if (returnValue instanceof ScimUser) {
+ if (returnValue instanceof ScimCore) {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
- response.addHeader("ETag", "\"" + ((ScimUser) returnValue).getVersion() + "\"");
+ response.addHeader("ETag", "\"" + ((ScimCore) returnValue).getVersion() + "\"");
}
super.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
View
4 common/src/main/java/org/cloudfoundry/identity/uaa/error/UaaException.java
@@ -54,6 +54,10 @@ public UaaException(String msg) {
this(DEFAULT_ERROR, msg, 400);
}
+ public UaaException(String msg, int status) {
+ this(DEFAULT_ERROR, msg, status);
+ }
+
public UaaException(String error, String description, int status) {
super(description);
this.error = error;
View
4 common/src/main/java/org/cloudfoundry/identity/uaa/integration/UaaTestAccounts.java
@@ -130,10 +130,6 @@ public String getVarzAuthorizationHeader() {
return getAuthorizationHeader("varz", "varz", "varzclientsecret");
}
- public String getBatchAuthorizationHeader() {
- return getAuthorizationHeader("batch", "batch", "batchsecret");
- }
-
public String getAuthorizationHeader(String prefix, String defaultUsername, String defaultPassword) {
String username = environment.getProperty(prefix + ".username", defaultUsername);
String password = environment.getProperty(prefix + ".password", defaultPassword);
View
7 common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java
@@ -22,6 +22,7 @@
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.BaseClientDetails;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.stereotype.Controller;
@@ -68,9 +69,11 @@ public String confirm(Map<String, Object> model, final HttpServletRequest reques
// response.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
else {
- ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
+ BaseClientDetails client = new BaseClientDetails(clientDetailsService.loadClientByClientId(clientAuth.getClientId()));
+ client.setClientSecret(null);
model.put("auth_request", clientAuth);
- model.put("client", client);
+ model.put("client", client); // TODO: remove this once it has gone from jsp pages
+ model.put("client_id", clientAuth.getClientId());
model.put("redirect_uri", getRedirectUri(client, clientAuth));
model.put("scopes", getScopes(client, clientAuth));
model.put("message",
View
30 common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpoints.java
@@ -25,6 +25,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.cloudfoundry.identity.uaa.rest.SimpleMessage;
import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor;
import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor;
import org.cloudfoundry.identity.uaa.util.UaaStringUtils;
@@ -153,14 +154,18 @@ public ClientDetails getClientDetails(@PathVariable String client) throws Except
}
@RequestMapping(value = "/oauth/clients", method = RequestMethod.POST)
- public ResponseEntity<Void> createClientDetails(@RequestBody BaseClientDetails client) throws Exception {
+ @ResponseStatus(HttpStatus.CREATED)
+ @ResponseBody
+ public ClientDetails createClientDetails(@RequestBody BaseClientDetails client) throws Exception {
ClientDetails details = validateClient(client, true);
clientRegistrationService.addClientDetails(details);
- return new ResponseEntity<Void>(HttpStatus.CREATED);
+ return removeSecret(client);
}
@RequestMapping(value = "/oauth/clients/{client}", method = RequestMethod.PUT)
- public ResponseEntity<Void> updateClientDetails(@RequestBody BaseClientDetails client,
+ @ResponseStatus(HttpStatus.OK)
+ @ResponseBody
+ public ClientDetails updateClientDetails(@RequestBody BaseClientDetails client,
@PathVariable("client") String clientId) throws Exception {
Assert.state(clientId.equals(client.getClientId()),
String.format("The client id (%s) does not match the URL (%s)", client.getClientId(), clientId));
@@ -175,29 +180,32 @@ public ClientDetails getClientDetails(@PathVariable String client) throws Except
details = validateClient(details, false);
clientRegistrationService.updateClientDetails(details);
clientUpdates.incrementAndGet();
- return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
+ return removeSecret(client);
}
@RequestMapping(value = "/oauth/clients/{client}", method = RequestMethod.DELETE)
- public ResponseEntity<Void> removeClientDetails(@PathVariable String client) throws Exception {
+ @ResponseStatus(HttpStatus.OK)
+ @ResponseBody
+ public ClientDetails removeClientDetails(@PathVariable String client) throws Exception {
+ ClientDetails details = clientDetailsService.loadClientByClientId(client);
clientRegistrationService.removeClientDetails(client);
clientDeletes.incrementAndGet();
- return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
+ return removeSecret(details);
}
@RequestMapping(value = "/oauth/clients", method = RequestMethod.GET)
- public ResponseEntity<Map<String, ClientDetails>> listClientDetails() throws Exception {
+ @ResponseBody
+ public Map<String, ClientDetails> listClientDetails() throws Exception {
List<ClientDetails> details = clientRegistrationService.listClientDetails();
Map<String, ClientDetails> map = new LinkedHashMap<String, ClientDetails>();
for (ClientDetails client : details) {
map.put(client.getClientId(), removeSecret(client));
}
- return new ResponseEntity<Map<String, ClientDetails>>(map, HttpStatus.OK);
+ return map;
}
@RequestMapping(value = "/oauth/clients/{client}/secret", method = RequestMethod.PUT)
- @ResponseStatus(HttpStatus.NO_CONTENT)
- public void changeSecret(@PathVariable String client, @RequestBody SecretChangeRequest change) {
+ public SimpleMessage changeSecret(@PathVariable String client, @RequestBody SecretChangeRequest change) {
ClientDetails clientDetails;
try {
@@ -212,6 +220,8 @@ public void changeSecret(@PathVariable String client, @RequestBody SecretChangeR
clientRegistrationService.updateClientSecret(client, change.getSecret());
clientSecretChanges.incrementAndGet();
+
+ return new SimpleMessage("ok", "secret updated");
}
@ExceptionHandler(InvalidClientDetailsException.class)
View
60 common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientInfoEndpoint.java
@@ -0,0 +1,60 @@
+/*
+ * Cloud Foundry 2012.02.03 Beta
+ * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
+ *
+ * This product is licensed to you under the Apache License, Version 2.0 (the "License").
+ * You may not use this product except in compliance with the License.
+ *
+ * This product includes a number of subcomponents with
+ * separate copyright notices and license terms. Your use of these
+ * subcomponents is subject to the terms and conditions of the
+ * subcomponent's license, as noted in the LICENSE file.
+ */
+package org.cloudfoundry.identity.uaa.oauth;
+
+import java.security.Principal;
+import java.util.Collections;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.security.oauth2.provider.BaseClientDetails;
+import org.springframework.security.oauth2.provider.ClientDetails;
+import org.springframework.security.oauth2.provider.ClientDetailsService;
+import org.springframework.stereotype.Controller;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * Controller which allows clients to inspect their own registration data.
+ *
+ * @author Dave Syer
+ */
+@Controller
+public class ClientInfoEndpoint implements InitializingBean {
+
+ private ClientDetailsService clientDetailsService;
+
+ /**
+ * @param clientDetailsService the clientDetailsService to set
+ */
+ public void setClientDetailsService(ClientDetailsService clientDetailsService) {
+ this.clientDetailsService = clientDetailsService;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(clientDetailsService, "clientDetailsService must be set");
+ }
+
+ @RequestMapping(value = "/clientinfo")
+ @ResponseBody
+ public ClientDetails clientinfo(Principal principal) {
+
+ String clientId = principal.getName();
+ BaseClientDetails client = new BaseClientDetails(clientDetailsService.loadClientByClientId(clientId));
+ client.setClientSecret(null);
+ client.setAdditionalInformation(Collections.<String, Object> emptyMap());
+ return client;
+
+ }
+
+}
View
10 ...y/uaa/scim/job/InvalidEmailException.java → ...ntity/uaa/oauth/NoSuchTokenException.java
@@ -11,16 +11,18 @@
* subcomponent's license, as noted in the LICENSE file.
*/
-package org.cloudfoundry.identity.uaa.scim.job;
+package org.cloudfoundry.identity.uaa.oauth;
/**
+ * Exception for token admin when attempting to use a value that could not be found.
+ *
* @author Dave Syer
*
*/
-public class InvalidEmailException extends RuntimeException {
+public class NoSuchTokenException extends RuntimeException {
- public InvalidEmailException(String msg) {
- super(msg);
+ public NoSuchTokenException(String message) {
+ super(message);
}
}
View
40 common/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenAdminEndpoints.java
@@ -18,9 +18,10 @@
import java.util.HashMap;
import java.util.Map;
+import org.cloudfoundry.identity.uaa.rest.SimpleMessage;
+import org.cloudfoundry.identity.uaa.scim.ScimResourceNotFoundException;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning;
-import org.cloudfoundry.identity.uaa.scim.UserNotFoundException;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -35,6 +36,7 @@
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -65,21 +67,16 @@
}
@RequestMapping(value = "/oauth/users/{user}/tokens/{token}", method = RequestMethod.DELETE)
- public ResponseEntity<?> revokeUserToken(@PathVariable String user, @PathVariable String token,
- Principal principal, @RequestParam(required = false, defaultValue = "true") boolean lookup)
- throws Exception {
+ @ResponseBody
+ public SimpleMessage revokeUserToken(@PathVariable String user, @PathVariable String token, Principal principal,
+ @RequestParam(required = false, defaultValue = "true") boolean lookup) throws Exception {
String username = lookup ? getUserName(user) : user;
checkResourceOwner(username, principal);
String tokenValue = getTokenValue(tokenServices.findTokensByUserName(username), token);
if (tokenValue != null && tokenServices.revokeToken(tokenValue)) {
- return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
- }
- else {
- Map<String, String> map = new HashMap<String, String>();
- map.put("error", "not_found");
- map.put("error_description", "Token not found");
- return new ResponseEntity<Map<String, String>>(map, HttpStatus.NOT_FOUND);
+ return new SimpleMessage("ok", "user token revoked");
}
+ throw new NoSuchTokenException("Token not found");
}
@RequestMapping("/oauth/clients/{client}/tokens")
@@ -91,19 +88,20 @@
}
@RequestMapping(value = "/oauth/clients/{client}/tokens/{token}", method = RequestMethod.DELETE)
- public ResponseEntity<?> revokeClientToken(@PathVariable String client, @PathVariable String token,
- Principal principal) throws Exception {
+ @ResponseBody
+ public SimpleMessage revokeClientToken(@PathVariable String client, @PathVariable String token, Principal principal)
+ throws Exception {
checkClient(client, principal);
String tokenValue = getTokenValue(tokenServices.findTokensByClientId(client), token);
if (tokenValue != null && tokenServices.revokeToken(tokenValue)) {
- return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
- }
- else {
- Map<String, String> map = new HashMap<String, String>();
- map.put("error", "not_found");
- map.put("error_description", "Token not found");
- return new ResponseEntity<Map<String, String>>(map, HttpStatus.NOT_FOUND);
+ return new SimpleMessage("ok", "client token revoked");
}
+ throw new NoSuchTokenException("Token not found");
+ }
+
+ @ExceptionHandler(NoSuchTokenException.class)
+ public ResponseEntity<Void> handleNoSuchToken(NoSuchTokenException e) {
+ return new ResponseEntity<Void>(HttpStatus.NOT_FOUND);
}
private String getUserName(String user) {
@@ -120,7 +118,7 @@ private String getUserName(String user) {
}
}
}
- catch (UserNotFoundException e) {
+ catch (ScimResourceNotFoundException e) {
// ignore
}
return username;
View
5 common/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordCheckEndpoint.java
@@ -14,6 +14,7 @@
package org.cloudfoundry.identity.uaa.password;
import org.springframework.stereotype.Controller;
+import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@@ -35,7 +36,7 @@ public void setScoreCalculator(PasswordScoreCalculator scoreCalculator) {
@RequestMapping(value = "/password/score", method = RequestMethod.POST)
@ResponseBody
- public PasswordScore passwordScore(@RequestParam String password) {
- return scoreCalculator.computeScore(password);
+ public PasswordScore passwordScore(@RequestParam String password, @RequestParam(defaultValue = "") String userData) {
+ return scoreCalculator.computeScore(password, StringUtils.commaDelimitedListToStringArray(userData));
}
}
View
4 common/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordScoreCalculator.java
@@ -13,6 +13,7 @@
package org.cloudfoundry.identity.uaa.password;
+
/**
* Computes a strength/score for a given password
*
@@ -22,7 +23,8 @@
/**
* @param password the trial password
+ * @param userData user-specific data which should not be in the password.
* @return the score computed for the password
*/
- public PasswordScore computeScore(String password);
+ public PasswordScore computeScore(String password, String... userData);
}
View
6 ...n/src/main/java/org/cloudfoundry/identity/uaa/password/ZxcvbnPasswordScoreCalculator.java
@@ -15,6 +15,8 @@
import static szxcvbn.ZxcvbnHelper.*;
+import java.util.Arrays;
+
/**
* A PasswordScoreCalculator that uses the Zxcvbn scala library to compute the strength of a given password.
* Uses a configurable 'requiredScore' property to flag a password as (un)acceptable.
@@ -29,8 +31,8 @@ public ZxcvbnPasswordScoreCalculator(int requiredScore) {
}
@Override
- public PasswordScore computeScore(String password) {
- int score = zxcvbn(password).score();
+ public PasswordScore computeScore(String password, String... userData) {
+ int score = zxcvbn(password, Arrays.asList(userData)).score();
return new PasswordScore(score, requiredScore);
}
}
View
62 common/src/main/java/org/cloudfoundry/identity/uaa/rest/SimpleMessage.java
@@ -0,0 +1,62 @@
+/*
+ * Cloud Foundry 2012.02.03 Beta
+ * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
+ *
+ * This product is licensed to you under the Apache License, Version 2.0 (the "License").
+ * You may not use this product except in compliance with the License.
+ *
+ * This product includes a number of subcomponents with
+ * separate copyright notices and license terms. Your use of these
+ * subcomponents is subject to the terms and conditions of the
+ * subcomponent's license, as noted in the LICENSE file.
+ */
+
+package org.cloudfoundry.identity.uaa.rest;
+
+import java.io.Serializable;
+
+/**
+ * Simple wrapper class for vanilla informational responses from REST endpoints.
+ *
+ * @author Dave Syer
+ *
+ */
+public class SimpleMessage implements Serializable {
+
+ private String status;
+
+ private String message;
+
+ @SuppressWarnings("unused")
+ private SimpleMessage() {
+ }
+
+ public SimpleMessage(String status, String message) {
+ this.status = status;
+ this.message = message;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ return "{\"status\"=\"" + status + "\",\"message\"=\"" + message + "\"}";
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof SimpleMessage && toString().equals(obj.toString());
+ }
+
+}
View
16 common/src/main/java/org/cloudfoundry/identity/uaa/scim/AttributeNameMapper.java
@@ -0,0 +1,16 @@
+package org.cloudfoundry.identity.uaa.scim;
+
+/**
+ * Helper to map attribute names between json requests/responses and internal names that make sense on the server.
+ */
+public interface AttributeNameMapper {
+
+ String mapToInternal(String attr);
+
+ String[] mapToInternal(String[] attr);
+
+ String mapFromInternal(String attr);
+
+ String[] mapFromInternal(String[] attr);
+
+}
View
4 ...entity/uaa/scim/InvalidUserException.java → ...aa/scim/InvalidScimResourceException.java
@@ -20,12 +20,12 @@
* @author Dave Syer
*
*/
-public class InvalidUserException extends ScimException {
+public class InvalidScimResourceException extends ScimException {
/**
* @param message a message for the caller
*/
- public InvalidUserException(String message) {
+ public InvalidScimResourceException(String message) {
super(message, HttpStatus.BAD_REQUEST);
}
View
117 common/src/main/java/org/cloudfoundry/identity/uaa/scim/JdbcPagingList.java
@@ -1,28 +1,36 @@
/*
- * Cloud Foundry 2012.02.03 Beta
- * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
- *
- * This product is licensed to you under the Apache License, Version 2.0 (the "License").
- * You may not use this product except in compliance with the License.
- *
- * This product includes a number of subcomponents with
- * separate copyright notices and license terms. Your use of these
- * subcomponents is subject to the terms and conditions of the
- * subcomponent's license, as noted in the LICENSE file.
+ * Cloud Foundry 2012.02.03 Beta Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
+ *
+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). You may not use this product
+ * except in compliance with the License.
+ *
+ * This product includes a number of subcomponents with separate copyright notices and license terms. Your use of these
+ * subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file.
*/
package org.cloudfoundry.identity.uaa.scim;
import java.util.AbstractList;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.NoSuchElementException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
/**
+ * <p>
+ * List implementation backed by a database query, allowing iteration and sublist operations without pulling the wole
+ * dataset into memory.
+ * </p>
+ *
+ * <p>
+ * Not thread safe.
+ * </p>
+ *
* @author Dave Syer
*
*/
@@ -70,9 +78,14 @@ public E get(int index) {
}
@Override
+ public Iterator<E> iterator() {
+ return new SafeIterator<E>(super.iterator());
+ }
+
+ @Override
public List<E> subList(int fromIndex, int toIndex) {
int end = toIndex > size ? size : toIndex;
- return super.subList(fromIndex, end);
+ return new SafeIteratorList<E>(super.subList(fromIndex, end));
}
private String getLimitSql(String sql, int index, int size) {
@@ -92,4 +105,86 @@ public int size() {
return this.size;
}
+ /**
+ * <p>
+ * A list whose iterators are safe from changes in the underlying list. The size is not always accurate if the
+ * underlying list changes, but the iterator will never say it has more elements and then fail when iterated.
+ * </p>
+ *
+ * <p>
+ * Not thread safe.
+ * </p>
+ *
+ * @author Dave Syer
+ *
+ * @param <T> the element type
+ */
+ private static class SafeIteratorList<T> extends AbstractList<T> {
+
+ private final List<T> list;
+
+ public SafeIteratorList(List<T> list) {
+ this.list = list;
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return new SafeIterator<T>(super.iterator());
+ }
+
+ @Override
+ public T get(int index) {
+ return list.get(index);
+ }
+
+ @Override
+ public int size() {
+ return list.size();
+ }
+ }
+
+ private static class SafeIterator<T> implements Iterator<T> {
+
+ private final Iterator<T> iterator;
+
+ private boolean polled = false;
+
+ private boolean hasNext = false;
+
+ private T next;
+
+ public SafeIterator(Iterator<T> iterator) {
+ this.iterator = iterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (!polled) {
+ polled = true;
+ try {
+ next = iterator.next();
+ hasNext = true;
+ return true;
+ } catch (NoSuchElementException e) {
+ hasNext = false;
+ return false;
+ }
+ }
+ return hasNext;
+ }
+
+ @Override
+ public T next() {
+ if (hasNext()) {
+ polled = false;
+ return next;
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Not supported: readonly interator");
+ }
+ }
}
View
259 common/src/main/java/org/cloudfoundry/identity/uaa/scim/JdbcScimUserProvisioning.java
@@ -17,26 +17,15 @@
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
import java.util.UUID;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.cloudfoundry.identity.uaa.scim.ScimUser.Group;
-import org.cloudfoundry.identity.uaa.scim.ScimUser.Meta;
import org.cloudfoundry.identity.uaa.scim.ScimUser.Name;
-import org.cloudfoundry.identity.uaa.user.UaaAuthority;
+import org.cloudfoundry.identity.uaa.scim.SearchQueryConverter.ProcessedFilter;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -49,7 +38,6 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
/**
* @author Luke Taylor
@@ -59,12 +47,12 @@
private final Log logger = LogFactory.getLog(getClass());
- public static final String USER_FIELDS = "id,version,created,lastModified,username,email,givenName,familyName,active,authorities,phoneNumber";
+ public static final String USER_FIELDS = "id,version,created,lastModified,username,email,givenName,familyName,active,phoneNumber";
public static final String CREATE_USER_SQL = "insert into users (" + USER_FIELDS
- + ",password) values (?,?,?,?,?,?,?,?,?,?,?,?)";
+ + ",password) values (?,?,?,?,?,?,?,?,?,?,?)";
- public static final String UPDATE_USER_SQL = "update users set version=?, lastModified=?, email=?, givenName=?, familyName=?, active=?, authorities=?, phoneNumber=? where id=? and version=?";
+ public static final String UPDATE_USER_SQL = "update users set version=?, lastModified=?, userName=?, email=?, givenName=?, familyName=?, active=?, phoneNumber=? where id=? and version=?";
public static final String DEACTIVATE_USER_SQL = "update users set active=false where id=?";
@@ -80,42 +68,10 @@
public static final String ALL_USERS = "select " + USER_FIELDS + " from users";
- /*
- * Filter regexes for turning SCIM filters into SQL:
- */
-
- static final Pattern emailsValuePattern = Pattern.compile("emails\\.value", Pattern.CASE_INSENSITIVE);
-
- static final Pattern groupsValuePattern = Pattern.compile("groups\\.display", Pattern.CASE_INSENSITIVE);
-
- static final Pattern phoneNumbersValuePattern = Pattern.compile("phoneNumbers\\.value", Pattern.CASE_INSENSITIVE);
-
- static final Pattern coPattern = Pattern.compile("(.*?)([a-z0-9]*) co '(.*?)'([\\s]*.*)", Pattern.CASE_INSENSITIVE);
-
- static final Pattern swPattern = Pattern.compile("(.*?)([a-z0-9]*) sw '(.*?)'([\\s]*.*)", Pattern.CASE_INSENSITIVE);
-
- static final Pattern eqPattern = Pattern.compile("(.*?)([a-z0-9]*) eq '(.*?)'([\\s]*.*)", Pattern.CASE_INSENSITIVE);
-
- static final Pattern boPattern = Pattern.compile("(.*?)([a-z0-9]*) eq (true|false)([\\s]*.*)",
- Pattern.CASE_INSENSITIVE);
-
- static final Pattern metaPattern = Pattern.compile("(.*?)meta\\.([a-z0-9]*) (\\S) '(.*?)'([\\s]*.*)",
- Pattern.CASE_INSENSITIVE);
-
- static final Pattern prPattern = Pattern.compile(" pr([\\s]*)", Pattern.CASE_INSENSITIVE);
-
- static final Pattern gtPattern = Pattern.compile(" gt ", Pattern.CASE_INSENSITIVE);
-
- static final Pattern gePattern = Pattern.compile(" ge ", Pattern.CASE_INSENSITIVE);
-
- static final Pattern ltPattern = Pattern.compile(" lt ", Pattern.CASE_INSENSITIVE);
-
- static final Pattern lePattern = Pattern.compile(" le ", Pattern.CASE_INSENSITIVE);
+ private SearchQueryConverter queryConverter = new ScimSearchQueryConverter();
- static final Pattern unquotedEq = Pattern.compile("(id|username|email|givenName|familyName) eq [^'].*",
- Pattern.CASE_INSENSITIVE);
-
- private static final DateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+ static final Pattern unquotedEq = Pattern.compile("(id|username|email|givenName|familyName) = [^'].*",
+ Pattern.CASE_INSENSITIVE);
protected final JdbcTemplate jdbcTemplate;
@@ -132,6 +88,10 @@ public JdbcScimUserProvisioning(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
+ public void setQueryConverter(SearchQueryConverter queryConverter) {
+ this.queryConverter = queryConverter;
+ }
+
@Override
public ScimUser retrieveUser(String id) {
try {
@@ -139,7 +99,7 @@ public ScimUser retrieveUser(String id) {
return u;
}
catch (EmptyResultDataAccessException e) {
- throw new UserNotFoundException("User " + id + " does not exist");
+ throw new ScimResourceNotFoundException("User " + id + " does not exist");
}
}
@@ -157,57 +117,15 @@ public ScimUser retrieveUser(String id) {
@Override
public List<ScimUser> retrieveUsers(String filter, String sortBy, boolean ascending) {
- String where = filter;
-
- // Single quotes for literals
- where = where.replaceAll("\"", "'");
-
- if (unquotedEq.matcher(where).matches()) {
- throw new IllegalArgumentException("Eq argument in filter (" + filter + ") must be quoted");
- }
-
- if (sortBy != null) {
- // Need to add "asc" or "desc" explicitly to ensure that the pattern splitting below works
- where = where + " order by " + sortBy + (ascending ? " asc" : " desc");
- }
-
- // There is only one email address for now...
- where = StringUtils.arrayToDelimitedString(emailsValuePattern.split(where), "email");
- // There is only one field in groups for now...
- where = StringUtils.arrayToDelimitedString(groupsValuePattern.split(where), "authorities");
- // There is only one phone number for now...
- where = StringUtils.arrayToDelimitedString(phoneNumbersValuePattern.split(where), "phoneNumber");
-
- Map<String, Object> values = new HashMap<String, Object>();
-
- where = makeCaseInsensitive(where, coPattern, "%slower(%s) like :?%s", "%%%s%%", values);
- where = makeCaseInsensitive(where, swPattern, "%slower(%s) like :?%s", "%s%%", values);
- where = makeCaseInsensitive(where, eqPattern, "%slower(%s) = :?%s", "%s", values);
- where = makeBooleans(where, boPattern, "%s%s = :?%s", values);
- where = prPattern.matcher(where).replaceAll(" is not null$1");
- where = gtPattern.matcher(where).replaceAll(" > ");
- where = gePattern.matcher(where).replaceAll(" >= ");
- where = ltPattern.matcher(where).replaceAll(" < ");
- where = lePattern.matcher(where).replaceAll(" <= ");
- // This will catch equality of number literals
- where = where.replaceAll(" eq ", " = ");
- where = makeTimestamps(where, metaPattern, "%s%s %s :?%s", values);
- where = where.replaceAll("meta\\.", "");
-
- logger.debug("Filtering users with SQL: [" + where + "], and parameters: " + values);
-
- if (where.contains("emails.")) {
- throw new UnsupportedOperationException("Filters on email address fields other than 'value' not supported");
- }
- if (where.contains("phoneNumbers.")) {
- throw new UnsupportedOperationException("Filters on phone number fields other than 'value' not supported");
- }
+ ProcessedFilter where = queryConverter.convert(filter, sortBy, ascending);
+ logger.debug("Filtering users with SQL: " + where);
+ validateSqlWhereClause(where.getSql());
try {
- // Default order is by created date descending
- String order = sortBy == null ? " ORDER BY created desc" : "";
- return new JdbcPagingList<ScimUser>(jdbcTemplate, ALL_USERS + " WHERE " + where + order, values, mapper,
+ String completeSql = ALL_USERS + " where " + where.getSql();
+ logger.debug("complete sql: " + completeSql + ", params: " + where.getParams());
+ return new JdbcPagingList<ScimUser>(jdbcTemplate, ALL_USERS + " where " + where.getSql(), where.getParams(), mapper,
200);
}
catch (DataAccessException e) {
@@ -216,69 +134,28 @@ public ScimUser retrieveUser(String id) {
}
}
- private String makeTimestamps(String where, Pattern pattern, String template, Map<String, Object> values) {
- String output = where;
- Matcher matcher = pattern.matcher(output);
- int count = values.size();
- while (matcher.matches()) {
- String property = matcher.group(2);
- Object value = matcher.group(4);
- if (property.equals("created") || property.equals("lastModified")) {
- try {
- value = TIMESTAMP_FORMAT.parse((String) value);
- }
- catch (ParseException e) {
- // ignore
- }
- }
- values.put("value" + count, value);
- String query = template.replace("?", "value" + count);
- output = matcher.replaceFirst(String.format(query, matcher.group(1), property, matcher.group(3),
- matcher.group(5)));
- matcher = pattern.matcher(output);
- count++;
+ private void validateSqlWhereClause(String where) {
+ if (unquotedEq.matcher(where).matches()) {
+ throw new IllegalArgumentException("Eq argument in filter must be quoted");
}
- return output;
- }
- private String makeCaseInsensitive(String where, Pattern pattern, String template, String valueTemplate,
- Map<String, Object> values) {
- String output = where;
- Matcher matcher = pattern.matcher(output);
- int count = values.size();
- while (matcher.matches()) {
- values.put("value" + count, String.format(valueTemplate, matcher.group(3).toLowerCase()));
- String query = template.replace("?", "value" + count);
- output = matcher.replaceFirst(String.format(query, matcher.group(1), matcher.group(2), matcher.group(4)));
- matcher = pattern.matcher(output);
- count++;
+ if (where.contains("emails.")) {
+ throw new UnsupportedOperationException("Filters on email address fields other than 'value' not supported");
}
- return output;
- }
- private String makeBooleans(String where, Pattern pattern, String template, Map<String, Object> values) {
- String output = where;
- Matcher matcher = pattern.matcher(output);
- int count = values.size();
- while (matcher.matches()) {
- values.put("value" + count, Boolean.valueOf(matcher.group(3).toLowerCase()));
- String query = template.replace("?", "value" + count);
- output = matcher.replaceFirst(String.format(query, matcher.group(1), matcher.group(2), matcher.group(4)));
- matcher = pattern.matcher(output);
- count++;
+ if (where.contains("phoneNumbers.")) {
+ throw new UnsupportedOperationException("Filters on phone number fields other than 'value' not supported");
}
- return output;
}
@Override
public ScimUser createUser(final ScimUser user, final String password) throws InvalidPasswordException,
- InvalidUserException {
+ InvalidScimResourceException {
passwordValidator.validate(password, user);
validate(user);
logger.info("Creating new user: " + user.getUserName());
- final String authorities = getAuthorities(user);
final String id = UUID.randomUUID().toString();
try {
@@ -293,44 +170,30 @@ public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(7, user.getName().getGivenName());
ps.setString(8, user.getName().getFamilyName());
ps.setBoolean(9, user.isActive());
- ps.setString(10, authorities);
String phoneNumber = extractPhoneNumber(user);
- ps.setString(11, phoneNumber);
- ps.setString(12, passwordEncoder.encode(password));
+ ps.setString(10, phoneNumber);
+ ps.setString(11, passwordEncoder.encode(password));
}
});
}
catch (DuplicateKeyException e) {
- throw new UserAlreadyExistsException("Username already in use (could be inactive account): "
+ throw new ScimResourceAlreadyExistsException("Username already in use (could be inactive account): "
+ user.getUserName());
}
return retrieveUser(id);
}
- private String getAuthorities(ScimUser user) {
- // Preserve simple implementation based only on uaa user type
- normalizeGroups(user);
- Set<String> set = new LinkedHashSet<String>();
- // Augment with explicit group membership
- if (user.getGroups()!=null) {
- for (Group group : user.getGroups()) {
- set.add(group.getDisplay());
- }
- }
- return StringUtils.collectionToCommaDelimitedString(set);
- }
-
- private void validate(final ScimUser user) throws InvalidUserException {
+ private void validate(final ScimUser user) throws InvalidScimResourceException {
if (!user.getUserName().matches("[a-z0-9+-_.@]+")) {
- throw new InvalidUserException("Username must be lower case alphanumeric with optional characters '._@'.");
+ throw new InvalidScimResourceException("Username must be lower case alphanumeric with optional characters '._@'.");
}
if (user.getEmails()==null || user.getEmails().isEmpty()) {
- throw new InvalidUserException("An email must be provided.");
+ throw new InvalidScimResourceException("An email must be provided.");
}
if (user.getName()==null || user.getName().getFamilyName()==null || user.getName().getGivenName()==null) {
- throw new InvalidUserException("A given name and a family name must be provided.");
+ throw new InvalidScimResourceException("A given name and a family name must be provided.");
}
}
@@ -343,20 +206,19 @@ private String extractPhoneNumber(final ScimUser user) {
}
@Override
- public ScimUser updateUser(final String id, final ScimUser user) throws InvalidUserException {
+ public ScimUser updateUser(final String id, final ScimUser user) throws InvalidScimResourceException {
validate(user);
logger.info("Updating user " + user.getUserName());
- final String authorities = getAuthorities(user);
-
+
int updated = jdbcTemplate.update(UPDATE_USER_SQL, new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
ps.setInt(1, user.getVersion() + 1);
ps.setTimestamp(2, new Timestamp(new Date().getTime()));
- ps.setString(3, user.getPrimaryEmail());
- ps.setString(4, user.getName().getGivenName());
- ps.setString(5, user.getName().getFamilyName());
- ps.setBoolean(6, user.isActive());
- ps.setString(7, authorities);
+ ps.setString(3, user.getUserName());
+ ps.setString(4, user.getPrimaryEmail());
+ ps.setString(5, user.getName().getGivenName());
+ ps.setString(6, user.getName().getFamilyName());
+ ps.setBoolean(7, user.isActive());
ps.setString(8, extractPhoneNumber(user));
ps.setString(9, id);
ps.setInt(10, user.getVersion());
@@ -376,7 +238,7 @@ public void setValues(PreparedStatement ps) throws SQLException {
@Override
public boolean changePassword(final String id, String oldPassword, final String newPassword)
- throws UserNotFoundException {
+ throws ScimResourceNotFoundException {
if (oldPassword != null) {
checkPasswordMatches(id, oldPassword);
}
@@ -389,7 +251,7 @@ public void setValues(PreparedStatement ps) throws SQLException {
}
});
if (updated == 0) {
- throw new UserNotFoundException("User " + id + " does not exist");
+ throw new ScimResourceNotFoundException("User " + id + " does not exist");
}
if (updated != 1) {
throw new IncorrectResultSizeDataAccessException(1);
@@ -405,7 +267,7 @@ private void checkPasswordMatches(String id, String oldPassword) {
new int[] { Types.VARCHAR }, String.class);
}
catch (IncorrectResultSizeDataAccessException e) {
- throw new UserNotFoundException("User " + id + " does not exist");
+ throw new ScimResourceNotFoundException("User " + id + " does not exist");
}
if (!passwordEncoder.matches(oldPassword, currentPassword)) {
@@ -478,20 +340,6 @@ public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
- private void normalizeGroups(ScimUser user) {
- Set<Group> groups = new LinkedHashSet<Group>();
- if (user.getGroups()!=null) {
- groups.addAll(user.getGroups());
- }
- // Everyone is a user
- groups.add(new Group(null, UaaAuthority.UAA_USER.getAuthority()));
- if (user.getUserType()!=null && user.getUserType().contains("admin")) {
- // Some people are also admins
- groups.add(new Group(null, UaaAuthority.UAA_ADMIN.getAuthority()));
- }
- user.setGroups(new ArrayList<Group>(groups));
- }
-
private static final class ScimUserRowMapper implements RowMapper<ScimUser> {
@Override
public ScimUser mapRow(ResultSet rs, int rowNum) throws SQLException {
@@ -504,11 +352,10 @@ public ScimUser mapRow(ResultSet rs, int rowNum) throws SQLException {
String givenName = rs.getString(7);
String familyName = rs.getString(8);
boolean active = rs.getBoolean(9);
- String authorities = rs.getString(10);
- String phoneNumber = rs.getString(11);
+ String phoneNumber = rs.getString(10);
ScimUser user = new ScimUser();
user.setId(id);
- Meta meta = new Meta();
+ ScimMeta meta = new ScimMeta();
meta.setVersion(version);
meta.setCreated(created);
meta.setLastModified(lastModified);
@@ -523,24 +370,8 @@ public ScimUser mapRow(ResultSet rs, int rowNum) throws SQLException {
name.setFamilyName(familyName);
user.setName(name);
user.setActive(active);
- setAuthorities(user, authorities);
return user;
}
-
- private void setAuthorities(ScimUser user, String authorities) {
- if (authorities==null) {
- return;
- }
- user.setUserType(UaaAuthority.fromAuthorities(authorities).getUserType());
- List<Group> groups = new ArrayList<Group>();
- for (String group : authorities.split(",")) {
- groups.add(new Group(null, group.trim()));
- }
- if (!groups.isEmpty()) {
- user.setGroups(groups);
- }
- }
-
}
}
View
10 common/src/main/java/org/cloudfoundry/identity/uaa/scim/RemoteScimUserProvisioning.java
@@ -48,7 +48,7 @@ public void setBaseUrl(String baseUrl) {
}
@Override
- public ScimUser retrieveUser(String id) throws UserNotFoundException {
+ public ScimUser retrieveUser(String id) throws ScimResourceNotFoundException {
return restTemplate.getForObject(baseUrl + "/User/{id}", ScimUser.class, id);
}
@@ -72,19 +72,19 @@ public ScimUser retrieveUser(String id) throws UserNotFoundException {
}
@Override
- public ScimUser createUser(ScimUser user, String password) throws InvalidPasswordException, InvalidUserException {
+ public ScimUser createUser(ScimUser user, String password) throws InvalidPasswordException, InvalidScimResourceException {
user.setPassword(password);
return restTemplate.postForObject(baseUrl + "/User", user, ScimUser.class);
}
@Override
- public ScimUser updateUser(String id, ScimUser user) throws InvalidUserException, UserNotFoundException {
+ public ScimUser updateUser(String id, ScimUser user) throws InvalidScimResourceException, ScimResourceNotFoundException {
restTemplate.put(baseUrl + "/User/{id}", user, id);
return user;
}
@Override
- public boolean changePassword(String id, String oldPassword, String newPassword) throws UserNotFoundException {
+ public boolean changePassword(String id, String oldPassword, String newPassword) throws ScimResourceNotFoundException {
PasswordChangeRequest request = new PasswordChangeRequest();
request.setOldPassword(oldPassword);
request.setPassword(newPassword);
@@ -93,7 +93,7 @@ public boolean changePassword(String id, String oldPassword, String newPassword)
}
@Override
- public ScimUser removeUser(String id, int version) throws UserNotFoundException {
+ public ScimUser removeUser(String id, int version) throws ScimResourceNotFoundException {
HttpHeaders headers = new HttpHeaders();
headers.set("If-Match", String.format("%d", version));
return restTemplate.exchange(baseUrl + "/User/{id}", HttpMethod.DELETE, new HttpEntity<Void>(headers), ScimUser.class, id).getBody();
View
83 common/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java
@@ -0,0 +1,83 @@
+package org.cloudfoundry.identity.uaa.scim;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.springframework.util.Assert;
+
+import java.util.Arrays;
+
+public abstract class ScimCore {
+
+ public static final String[] SCHEMAS = new String[]{"urn:scim:schemas:core:1.0"};
+
+ private String id;
+
+ private String externalId;
+
+ private ScimMeta meta = new ScimMeta();
+
+ protected ScimCore(String id) {
+ this.id = id;
+ }
+
+ protected ScimCore() {
+ }
+
+ public void setSchemas(String[] schemas) {
+ Assert.isTrue(Arrays.equals(SCHEMAS, schemas), "Only schema '" + SCHEMAS[0] + "' is currently supported");
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ void setId(String id) {
+ this.id = id;
+ }
+
+ public String getExternalId() {
+ return externalId;
+ }
+
+ public void setExternalId(String externalId) {
+ this.externalId = externalId;
+ }
+
+ public ScimMeta getMeta() {
+ return meta;
+ }
+
+ public void setMeta(ScimMeta meta) {
+ this.meta = meta;
+ }
+
+ @JsonIgnore
+ public void setVersion(int version) {
+ meta.setVersion(version);
+ }
+
+ @JsonIgnore
+ public int getVersion() {
+ return meta.getVersion();
+ }
+
+ public String[] getSchemas() {
+ return SCHEMAS;
+ }
+
+ @Override
+ public int hashCode() {
+ return id != null ? id.hashCode() : super.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ScimCore) {
+ ScimCore other = (ScimCore) o;
+ return id.equals(other.id);
+ } else if (o instanceof String) {
+ String otherId = (String) o;
+ return id.equals(otherId);
+ }
+ return false;
+ }
+}
View
54 common/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java
@@ -0,0 +1,54 @@
+package org.cloudfoundry.identity.uaa.scim;
+
+import org.cloudfoundry.identity.uaa.scim.json.JsonDateDeserializer;
+import org.cloudfoundry.identity.uaa.scim.json.JsonDateSerializer;
+import org.codehaus.jackson.map.annotate.JsonDeserialize;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+import java.util.Date;
+
+@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+public class ScimMeta {
+ private int version = 0;
+
+ private Date created = new Date();
+
+ private Date lastModified = null;
+
+ public ScimMeta() {
+ }
+
+ public ScimMeta(Date created, Date lastModified, int version) {
+ this.created = created;
+ this.lastModified = lastModified;
+ this.version = version;
+ }
+
+ @JsonSerialize(using = JsonDateSerializer.class, include = JsonSerialize.Inclusion.NON_NULL)
+ public Date getCreated() {
+ return created;
+ }
+
+ @JsonDeserialize(using = JsonDateDeserializer.class)
+ public void setCreated(Date created) {
+ this.created = created;
+ }
+
+ @JsonSerialize(using = JsonDateSerializer.class, include = JsonSerialize.Inclusion.NON_NULL)
+ public Date getLastModified() {
+ return lastModified;
+ }
+
+ @JsonDeserialize(using = JsonDateDeserializer.class)
+ public void setLastModified(Date lastModified) {
+ this.lastModified = lastModified;
+ }
+
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+}
View
4 .../uaa/scim/UserAlreadyExistsException.java → ...m/ScimResourceAlreadyExistsException.java
@@ -20,12 +20,12 @@
* @author Dave Syer
*
*/
-public class UserAlreadyExistsException extends ScimException {
+public class ScimResourceAlreadyExistsException extends ScimException {
/**
* @param message a message for the caller
*/
- public UserAlreadyExistsException(String message) {
+ public ScimResourceAlreadyExistsException(String message) {
super(message, HttpStatus.CONFLICT);
}
View
4 ...ntity/uaa/scim/UserConflictException.java → ...a/scim/ScimResourceConflictException.java
@@ -20,12 +20,12 @@
* @author Dave Syer
*
*/
-public class UserConflictException extends ScimException {
+public class ScimResourceConflictException extends ScimException {
/**
* @param message a message for the caller
*/
- public UserConflictException(String message) {
+ public ScimResourceConflictException(String message) {
super(message, HttpStatus.CONFLICT);
}
View
4 ...ntity/uaa/scim/UserNotFoundException.java → ...a/scim/ScimResourceNotFoundException.java
@@ -20,12 +20,12 @@
* @author Dave Syer
*
*/
-public class UserNotFoundException extends ScimException {
+public class ScimResourceNotFoundException extends ScimException {
/**
* @param message a message for the caller
*/
- public UserNotFoundException(String message) {
+ public ScimResourceNotFoundException(String message) {
super(message, HttpStatus.NOT_FOUND);
}
View
138 common/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimSearchQueryConverter.java
@@ -0,0 +1,138 @@
+package org.cloudfoundry.identity.uaa.scim;
+
+import org.springframework.util.StringUtils;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ScimSearchQueryConverter implements SearchQueryConverter {
+
+ static final Pattern coPattern = Pattern.compile("(.*?)([a-z0-9]*) co '(.*?)'([\\s]*.*)", Pattern.CASE_INSENSITIVE);
+
+ static final Pattern swPattern = Pattern.compile("(.*?)([a-z0-9]*) sw '(.*?)'([\\s]*.*)", Pattern.CASE_INSENSITIVE);
+
+ static final Pattern eqPattern = Pattern.compile("(.*?)([a-z0-9]*) eq '(.*?)'([\\s]*.*)", Pattern.CASE_INSENSITIVE);
+
+ static final Pattern boPattern = Pattern.compile("(.*?)([a-z0-9]*) eq (true|false)([\\s]*.*)", Pattern.CASE_INSENSITIVE);
+
+ static final Pattern metaPattern = Pattern.compile("(.*?)meta\\.([a-z0-9]*) (\\S) '(.*?)'([\\s]*.*)", Pattern.CASE_INSENSITIVE);
+
+ static final Pattern prPattern = Pattern.compile(" pr([\\s]*)", Pattern.CASE_INSENSITIVE);
+
+ static final Pattern gtPattern = Pattern.compile(" gt ", Pattern.CASE_INSENSITIVE);
+
+ static final Pattern gePattern = Pattern.compile(" ge ", Pattern.CASE_INSENSITIVE);
+
+ static final Pattern ltPattern = Pattern.compile(" lt ", Pattern.CASE_INSENSITIVE);
+
+ static final Pattern lePattern = Pattern.compile(" le ", Pattern.CASE_INSENSITIVE);
+
+ private static final DateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+
+ private AttributeNameMapper mapper = new SimpleAttributeNameMapper(Collections.<String, String> emptyMap());
+
+ public void setAttributeNameMapper(AttributeNameMapper mapper) {
+ this.mapper = mapper;
+ }
+
+ @Override
+ public ProcessedFilter convert(String filter, String sortBy, boolean ascending) {
+ return convert(filter, sortBy, ascending, mapper);