Merge branch 'master' into issue52

Conflicts:
	openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java
	openid-connect-server/src/main/webapp/WEB-INF/spring-servlet.xml
	openid-connect-server/src/main/webapp/WEB-INF/views/oauth/approve.jsp
pull/105/merge
Amanda Anganes 2012-06-11 15:04:01 -04:00
commit bbf9591c92
54 changed files with 979 additions and 371 deletions

View File

@ -1,3 +1,4 @@
#Wed May 30 14:51:48 EDT 2012
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true

View File

@ -1,4 +1,4 @@
#Mon May 07 14:38:46 EDT 2012
#Wed May 30 14:51:48 EDT 2012
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true

View File

@ -56,6 +56,6 @@ To test the default config, deploy to a servlet container, and request:
http://localhost:8080/account-chooser/?redirect_uri=http://www.google.com&client_id=FGWEUIASJK
Click **Submit** or **Cancel**, and you will be Google will open. Study the URL parameters of each.
Click **Submit** or **Cancel**, and Google will open. Study the URL parameters of each.
[Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client"
[Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client"

View File

@ -0,0 +1 @@
protocol.md

View File

@ -74,7 +74,7 @@ The following is non-normative example of a responses. Line wraps are for displa
A sequence diagram of a successful interaction would be:
![Success](./success.png)
![Success](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/raw/master/account-chooser/docs/success.png)
#### The Client ID is not Supported by the Account Chooser Application
@ -97,7 +97,7 @@ The following is a non-normative example. Line wraps after the second line are f
A sequence diagram of the interaction where the Account Chooser was not configured to support the Client would be:
![Not supported](./not_supported.png)
![Not supported](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/raw/master/account-chooser/docs/not_supported.png)
#### End-User refuses to select an Account
@ -119,7 +119,7 @@ The following is a non-normative example. Line wraps after the second line are f
A sequence diagram of an interaction where the End-User refused to select an Account would be:
![Cancel](./cancel.png)
![Cancel](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/raw/master/account-chooser/docs/cancel.png)
[OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html "OpenID Connect Standard 1.0"
[OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html#code_flow "Authorization Code Flow, OpenID Connect Standard"

View File

@ -21,4 +21,22 @@ Managing OAuth clients:
Modules
-------
The project uses a multi-level Maven and git repository sutrcture. The main project is split into the following modules:
- openid-connect-common: common classes, service and repository interfaces, and model code. Also includes full JWT library.
- openid-connect-server: IdP/server implementation, includes implementations of services and repositories for use by server.
- openid-connect-client: RP/client implementation, built around spring security filters.
- spring-security-oauth: Git submodule that points to the Spring Security OAuth Git repository. Will be removed once a reliable milestone is reached upstream (see note above).
Maven War Overlay
-----------------
One of the best ways to build a custom deployment of this system is to use the Maven War Overlay mechanism. In essence, you make a new Maven project with a "war" disposition and make it depend on the openid-connect-server module with the Maven Overlay plugin configured. Any files in your new project will be built and injected into the war from the other project. This action will also overwrite any existing files.
For instance, to overwrite the data source configuration in the main server war file, create a file named src/main/webapp/WEB-INF/data-context.xml that contains the dataSource bean. This file will completely replace the one that's in the originally built war.

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="org.eclipse.jst.component.nondependency" value=""/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="org.eclipse.jst.component.nondependency" value=""/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@ -1,3 +1,4 @@
#Wed May 30 14:51:48 EDT 2012
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true

View File

@ -2,11 +2,11 @@
## Overview
This is the Client, a Spring Security AuthenticationFilter, to OpenID Connect Java Spring Server described by [OpenID Connect Standard].
This is the Client, a Spring Security AuthenticationFilter, to the OpenID Connect Java Spring Server following the [OpenID Connect Standard] described protocol.
## Configuration of OIDCAuthenticationFilter
Configure the OIDCAuthenticationFilter by adding the XML to your application context security like so:
Configure the OIDCAuthenticationFilter by adding the XML to your application context security like so making changes where necessary for your deployment:
<security:http auto-config="false"
use-expressions="true"
@ -67,7 +67,7 @@ You will need to implement your own UserDetailsService and configure as the abov
The OIDCAuthenticationUsingChooserFilter was written in response to [Issue #39].
Th Authentication Filter use the *oidcServerConfigs* property, a map of OIDC servers, an *accountChooserURI* property to denote the URI of the Account Chooser, and an *accountChooserClient* property to identify the Client to the Account Chooser UI application like so:
The Authentication Filter uses the *oidcServerConfigs* property, a map of OIDC servers; an *accountChooserURI* property to denote the URI of the Account Chooser; and an *accountChooserClient* property to identify the Client to the Account Chooser UI application like so with modifications specific to your deployment:
<bean id="openIdConnectAuthenticationFilter"
class="org.mitre.openid.connect.client.OIDCAuthenticationUsingChooserFilter">
@ -98,7 +98,96 @@ Th Authentication Filter use the *oidcServerConfigs* property, a map of OIDC ser
Again, you will need to implement your own UserDetailsService and configure as the above does with the reference to *myUserDetailsService*.
## Implementing your own UserDetailsService
An example UserDetailsService for the Rave Portal follows:
package org.mitre.mpn.service.impl;
import org.apache.rave.portal.model.NewUser;
import org.apache.rave.portal.model.User;
import org.apache.rave.portal.service.NewAccountService;
import org.apache.rave.portal.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.mitre.openid.connect.client.OpenIdConnectAuthenticationToken;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service(value = "myUserDetailsService")
public class MyUserDetailsService implements UserDetailsService,
AuthenticationUserDetailsService<OpenIdConnectAuthenticationToken> {
private static final Logger log = LoggerFactory.getLogger(MpnUserDetailsService.class);
private final UserService userService;
private final NewAccountService newAccountService;
//TODO: This is temporarily hard-coded while we wait for the concept of Page Templates to be implemented in Rave
private static final String DEFAULT_LAYOUT_CODE = "columns_3";
@Autowired
public MyUserDetailsService(UserService userService, NewAccountService newAccountService) {
this.userService = userService;
this.newAccountService = newAccountService;
}
/* (non-Javadoc)
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.debug("loadUserByUsername called with: {}", username);
User user = userService.getUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User with username '" + username + "' was not found!");
}
return user;
}
/* (non-Javadoc)
* @see org.springframework.security.core.userdetails.AuthenticationUserDetailsService#loadUserDetails(org.springframework.security.core.Authentication)
*/
public UserDetails loadUserDetails(OpenIdConnectAuthenticationToken token) throws UsernameNotFoundException {
log.debug("loadUserDetails called with: {}", token);
User user = userService.getUserByUsername(token.getUserId());
if (user == null) {
NewUser newUser = new NewUser();
newUser.setUsername(token.getUserId());
newUser.setEmail(token.getUserId() + "@example.com");
newUser.setPageLayout(DEFAULT_LAYOUT_CODE);
newUser.setPassword(UUID.randomUUID().toString());
try {
newAccountService.createNewAccount(newUser);
} catch (Exception e) {
throw new RuntimeException(e);
}
user = userService.getUserByUsername(token.getName());
}
return user;
}
}
[OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html "OpenID Connect Standard 1.0"
[OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html#code_flow "Authorization Code Flow, OpenID Connect Standard"
[Issuer Identifier]: http://openid.net/specs/openid-connect-messages-1_0.html#issuer_identifier "Issuer Identifier"
[Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client"
[Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client"

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="org.eclipse.jst.component.nondependency" value=""/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="org.eclipse.jst.component.nondependency" value=""/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@ -1,3 +1,4 @@
#Wed May 30 14:51:48 EDT 2012
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true

View File

@ -89,20 +89,18 @@ public class JwtSigningAndValidationServiceDefault implements
Map<String, PublicKey> map = new HashMap<String, PublicKey>();
PublicKey publicKey;
for (JwtSigner signer : signers.values()) {
for (String signerId : signers.keySet()) {
JwtSigner signer = signers.get(signerId);
if (signer instanceof RsaSigner) {
publicKey = ((RsaSigner) signer).getPublicKey();
RsaSigner rsa = (RsaSigner)signer;
PublicKey publicKey = rsa.getPublicKey();
if (publicKey != null) {
// what's the index of this map for?
map.put(((RSAPublicKey) publicKey).getModulus()
.toString(16).toUpperCase()
+ ((RSAPublicKey) publicKey).getPublicExponent()
.toString(16).toUpperCase(), publicKey);
map.put(signerId, publicKey);
}
}

View File

@ -20,12 +20,13 @@ import java.util.List;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
public interface OAuth2TokenEntityService extends AuthorizationServerTokenServices, ResourceServerTokenServices {
public OAuth2AccessTokenEntity getAccessToken(String accessTokenValue);
public OAuth2AccessTokenEntity readAccessToken(String accessTokenValue);
public OAuth2RefreshTokenEntity getRefreshToken(String refreshTokenValue);
@ -43,4 +44,6 @@ public interface OAuth2TokenEntityService extends AuthorizationServerTokenServic
public OAuth2RefreshTokenEntity saveRefreshToken(OAuth2RefreshTokenEntity refreshToken);
public OAuth2AccessTokenEntity getAccessToken(OAuth2Authentication authentication);
}

View File

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@ -1,2 +1,3 @@
#Wed May 30 14:51:48 EDT 2012
com.springsource.sts.maven.maven.automatically.update=true
eclipse.preferences.version=1

View File

@ -1,7 +1,6 @@
CREATE TABLE clientdetails (
clientId VARCHAR(256),
clientSecret VARCHAR(2000),
registeredRedirectUri VARCHAR(2000),
clientName VARCHAR(256),
clientDescription VARCHAR(2000),
allowRefresh TINYINT,

View File

@ -1,4 +1,4 @@
CREATE TABLE redirect_uris (
owner_id VARCHAR(256),
registeredRedirectUri VARCHAR(256)
registeredRedirectUri VARCHAR(2000)
);

View File

@ -157,9 +157,13 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
@Override
public ClientDetailsEntity saveClient(ClientDetailsEntity client) {
if (client.getClientSecret().equals("")) {
client.setClientSecret(UUID.randomUUID().toString());
}
// this must be a new client if we don't have a clientID
// assign it a new ID
if (client.getClientId() == null || this.loadClientByClientId(client.getClientId()) == null) {
if (client.getClientId() == null || client.getClientId().equals("") || this.loadClientByClientId(client.getClientId()) == null) {
client.setClientId(UUID.randomUUID().toString());
return this.createClient(client);
} else {

View File

@ -228,8 +228,11 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
}
/**
* Get an access token from its token value.
*/
@Override
public OAuth2AccessTokenEntity getAccessToken(String accessTokenValue) throws AuthenticationException {
public OAuth2AccessTokenEntity readAccessToken(String accessTokenValue) throws AuthenticationException {
OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenByValue(accessTokenValue);
if (accessToken == null) {
throw new InvalidTokenException("Access token for value " + accessTokenValue + " was not found");
@ -239,6 +242,9 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
}
}
/**
* Get an access token by its authentication object.
*/
@Override
public OAuth2AccessTokenEntity getAccessToken(OAuth2Authentication authentication) {
@ -247,6 +253,9 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
return accessToken;
}
/**
* Get a refresh token by its token value.
*/
@Override
public OAuth2RefreshTokenEntity getRefreshToken(String refreshTokenValue) throws AuthenticationException {
OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenByValue(refreshTokenValue);
@ -258,12 +267,18 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
}
}
/**
* Revoke a refresh token and all access tokens issued to it.
*/
@Override
public void revokeRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
tokenRepository.clearAccessTokensForRefreshToken(refreshToken);
tokenRepository.removeRefreshToken(refreshToken);
}
/**
* Revoke an access token.
*/
@Override
public void revokeAccessToken(OAuth2AccessTokenEntity accessToken) {
tokenRepository.removeAccessToken(accessToken);
@ -352,12 +367,6 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
}
}
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
// TODO Auto-generated method stub
return null;
}
/* (non-Javadoc)
* @see org.mitre.oauth2.service.OAuth2TokenEntityService#saveAccessToken(org.mitre.oauth2.model.OAuth2AccessTokenEntity)
*/
@ -388,6 +397,4 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
this.tokenEnhancer = tokenEnhancer;
}
}

View File

@ -41,7 +41,7 @@ public class IntrospectionEndpoint {
@RequestMapping("/oauth/verify")
public ModelAndView verify(@RequestParam("token") String tokenValue,
ModelAndView modelAndView) {
OAuth2AccessTokenEntity token = tokenServices.getAccessToken(tokenValue);
OAuth2AccessTokenEntity token = tokenServices.readAccessToken(tokenValue);
if (token == null) {
// if it's not a valid token, we'll print a 404

View File

@ -22,6 +22,8 @@ import org.mitre.oauth2.exception.ClientNotFoundException;
import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.stereotype.Controller;

View File

@ -15,6 +15,8 @@
******************************************************************************/
package org.mitre.oauth2.web;
import java.security.Principal;
import org.mitre.oauth2.exception.PermissionDeniedException;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
@ -47,23 +49,35 @@ public class RevocationEndpoint {
// TODO
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
@RequestMapping("/oauth/revoke")
public ModelAndView revoke(@RequestParam("token") String tokenValue,
public ModelAndView revoke(@RequestParam("token") String tokenValue, Principal principal,
ModelAndView modelAndView) {
OAuth2RefreshTokenEntity refreshToken = tokenServices.getRefreshToken(tokenValue);
OAuth2AccessTokenEntity accessToken = tokenServices.getAccessToken(tokenValue);
OAuth2RefreshTokenEntity refreshToken = null;
OAuth2AccessTokenEntity accessToken = null;
try {
refreshToken = tokenServices.getRefreshToken(tokenValue);
} catch (InvalidTokenException e) {
// it's OK if either of these tokens are bad
}
try {
accessToken = tokenServices.readAccessToken(tokenValue);
} catch (InvalidTokenException e) {
// it's OK if either of these tokens are bad
}
if (refreshToken == null && accessToken == null) {
// TODO: this should throw a 400 with a JSON error code
throw new InvalidTokenException("Invalid OAuth token: " + tokenValue);
}
// TODO: there should be a way to do this in SPEL, right?
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth instanceof OAuth2Authentication) {
if (principal instanceof OAuth2Authentication) {
OAuth2AccessTokenEntity tok = tokenServices.getAccessToken((OAuth2Authentication) principal);
// we've got a client acting on its own behalf, not an admin
//ClientAuthentication clientAuth = (ClientAuthenticationToken) ((OAuth2Authentication) auth).getClientAuthentication();
AuthorizationRequest clientAuth = ((OAuth2Authentication) auth).getAuthorizationRequest();
AuthorizationRequest clientAuth = ((OAuth2Authentication) principal).getAuthorizationRequest();
if (refreshToken != null) {
if (!refreshToken.getClient().getClientId().equals(clientAuth.getClientId())) {

View File

@ -15,11 +15,20 @@
******************************************************************************/
package org.mitre.openid.connect.exception;
/**
* @author aanganes, nemonik
*
*/
public class ExpiredTokenException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
public ExpiredTokenException() {
super();
}
public ExpiredTokenException(String message) {
super(message);
}
}

View File

@ -15,11 +15,20 @@
******************************************************************************/
package org.mitre.openid.connect.exception;
/**
* @author aanganes, nemonik
*
*/
public class InvalidJwtIssuerException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
public InvalidJwtIssuerException() {
super();
}
public InvalidJwtIssuerException(String message) {
super(message);
}
}

View File

@ -15,11 +15,20 @@
******************************************************************************/
package org.mitre.openid.connect.exception;
/**
* @author aanganes, nemonik
*
*/
public class InvalidJwtSignatureException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
public InvalidJwtSignatureException() {
super();
}
public InvalidJwtSignatureException(String message) {
super(message);
}
}

View File

@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.mitre.openid.connect.exception;
/**
* @author aanganes, nemonik
*
*/
public class UnknownUserInfoSchemaException extends RuntimeException {
private static final long serialVersionUID = 1L;
public UnknownUserInfoSchemaException() {
super();
}
public UnknownUserInfoSchemaException(String message) {
super(message);
}
}

View File

@ -0,0 +1,59 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.mitre.openid.connect.view;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.view.AbstractView;
import com.google.gson.JsonObject;
/**
* @author nemonik
*
*/
public class ExceptionAsJSONView extends AbstractView {
/*
* (non-Javadoc)
*
* @see
* org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel
* (java.util.Map, javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest requesr, HttpServletResponse response)
throws Exception {
response.setContentType("application/json");
final JsonObject jsonObject = new JsonObject();
Object ex = model.get("exception");
jsonObject.addProperty("error", ex.getClass().getName());
jsonObject.addProperty("error_description",
((Exception) ex).getMessage());
response.getWriter().write(jsonObject.toString());
}
}

View File

@ -21,6 +21,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mitre.openid.connect.model.UserInfo;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.web.servlet.view.AbstractView;
@ -28,6 +29,7 @@ import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
public class JSONUserInfoView extends AbstractView{
@ -37,6 +39,8 @@ public class JSONUserInfoView extends AbstractView{
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
UserInfo userInfo = (UserInfo) model.get("userInfo");
Gson gson = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
@ -57,15 +61,45 @@ public class JSONUserInfoView extends AbstractView{
}).create();
response.setContentType("application/json");
Writer out = response.getWriter();
Object obj = model.get("entity");
if (obj == null) {
obj = model;
}
gson.toJson(obj, out);
gson.toJson(toJson(userInfo),out);
}
private JsonObject toJson(UserInfo ui) {
JsonObject obj = new JsonObject();
obj.addProperty("user_id", ui.getUserId());
obj.addProperty("name", ui.getName());
obj.addProperty("given_name", ui.getGivenName());
obj.addProperty("family_name", ui.getFamilyName());
obj.addProperty("middle_name", ui.getMiddleName());
obj.addProperty("nickname", ui.getNickname());
obj.addProperty("email", ui.getEmail());
obj.addProperty("profile", ui.getProfile());
obj.addProperty("picture", ui.getPicture());
obj.addProperty("email", ui.getEmail());
obj.addProperty("website", ui.getWebsite());
obj.addProperty("verified", ui.getVerified());
obj.addProperty("gender", ui.getGender());
obj.addProperty("zone_info", ui.getZoneinfo());
obj.addProperty("locale", ui.getLocale());
obj.addProperty("phone_number", ui.getPhoneNumber());
obj.addProperty("updated_time", ui.getUpdatedTime());
if (ui.getAddress() != null) {
JsonObject addr = new JsonObject();
addr.addProperty("formatted", ui.getAddress().getFormatted());
addr.addProperty("street_address", ui.getAddress().getStreetAddress());
addr.addProperty("locality", ui.getAddress().getLocality());
addr.addProperty("region", ui.getAddress().getRegion());
addr.addProperty("postal_code", ui.getAddress().getPostalCode());
addr.addProperty("country", ui.getAddress().getCountry());
obj.add("address", addr);
}
return obj;
}
}

View File

@ -33,10 +33,12 @@ import org.apache.commons.codec.binary.Base64;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.web.servlet.view.AbstractView;
import com.google.common.collect.BiMap;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
@ -67,50 +69,6 @@ public class JwkKeyListView extends AbstractView {
return false;
}
})
.registerTypeHierarchyAdapter(PublicKey.class, new JsonSerializer<PublicKey>() {
@Override
public JsonElement serialize(PublicKey src, Type typeOfSrc, JsonSerializationContext context) {
if (src instanceof RSAPublicKey) {
RSAPublicKey rsa = (RSAPublicKey)src;
BigInteger mod = rsa.getModulus();
BigInteger exp = rsa.getPublicExponent();
String m64 = Base64.encodeBase64URLSafeString(mod.toByteArray());
String e64 = Base64.encodeBase64URLSafeString(exp.toByteArray());
JsonObject o = new JsonObject();
o.addProperty("use", "sig");
o.addProperty("alg", "RSA");
o.addProperty("mod", m64);
o.addProperty("exp", e64);
// TODO: get the key ID from the map
return o;
} else if (src instanceof ECPublicKey) {
@SuppressWarnings("unused")
ECPublicKey ec = (ECPublicKey)src;
// TODO: serialize the EC
return null;
} else {
// skip this class ... we shouldn't have any keys in here that aren't encodable by this serializer
return null;
}
}
})
.create();
@ -119,10 +77,38 @@ public class JwkKeyListView extends AbstractView {
Writer out = response.getWriter();
Object obj = model.get("entity");
if (obj == null) {
obj = model;
}
BiMap<String, PublicKey> keyMap = (BiMap<String, PublicKey>) model.get("keys");
JsonObject obj = new JsonObject();
JsonArray keys = new JsonArray();
obj.add("keys", keys);
for (String keyId : keyMap.keySet()) {
PublicKey src = keyMap.get(keyId);
if (src instanceof RSAPublicKey) {
RSAPublicKey rsa = (RSAPublicKey)src;
BigInteger mod = rsa.getModulus();
BigInteger exp = rsa.getPublicExponent();
String m64 = Base64.encodeBase64URLSafeString(mod.toByteArray());
String e64 = Base64.encodeBase64URLSafeString(exp.toByteArray());
JsonObject o = new JsonObject();
o.addProperty("use", "sig"); // since we don't do encryption yet
o.addProperty("alg", "RSA"); // we know this is RSA
o.addProperty("mod", m64);
o.addProperty("exp", e64);
o.addProperty("kid", keyId);
keys.add(o);
}
}
gson.toJson(obj, out);

View File

@ -0,0 +1,154 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.mitre.openid.connect.view;
import java.io.Writer;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mitre.openid.connect.model.UserInfo;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.web.servlet.view.AbstractView;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
public class POCOUserInfoView extends AbstractView{
/* (non-Javadoc)
* @see org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel(java.util.Map, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
UserInfo userInfo = (UserInfo) model.get("userInfo");
Gson gson = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
public boolean shouldSkipField(FieldAttributes f) {
return false;
}
public boolean shouldSkipClass(Class<?> clazz) {
// skip the JPA binding wrapper
if (clazz.equals(BeanPropertyBindingResult.class)) {
return true;
}
return false;
}
}).create();
response.setContentType("application/json");
Writer out = response.getWriter();
gson.toJson(toPoco(userInfo),out);
}
private JsonObject toPoco(UserInfo ui) {
JsonObject poco = new JsonObject();
// Envelope Info
poco.addProperty("startIndex", 0);
poco.addProperty("itemsPerPage", 1);
poco.addProperty("totalResults", 1);
// Build the entry for this userInfo, then add it to entries, then add it to poco
JsonObject entry = new JsonObject();
entry.addProperty("id", ui.getUserId());
entry.addProperty("displayName", ui.getNickname());
if(ui.getFamilyName() != null
|| ui.getGivenName() != null
|| ui.getMiddleName() != null
|| ui.getName() != null) {
JsonObject name = new JsonObject();
name.addProperty("familyName", ui.getFamilyName());
name.addProperty("givenName", ui.getGivenName());
name.addProperty("middleName", ui.getMiddleName());
name.addProperty("formatted", ui.getName());
entry.add("name", name);
}
entry.addProperty("gender", ui.getGender());
if(ui.getEmail() != null) {
JsonObject email = new JsonObject();
email.addProperty("value", ui.getEmail());
JsonArray emailArray = new JsonArray();
emailArray.add(email);
entry.add("emails", emailArray);
}
if(ui.getPhoneNumber() != null){
JsonObject phone = new JsonObject();
phone.addProperty("value", ui.getPhoneNumber());
JsonArray phoneArray = new JsonArray();
phoneArray.add(phone);
entry.add("phoneNumbers", phoneArray);
}
if(ui.getPicture() != null){
JsonObject photo = new JsonObject();
photo.addProperty("value", ui.getPicture());
JsonArray photoArray = new JsonArray();
photoArray.add(photo);
entry.add("photos", photoArray);
}
if(ui.getWebsite() != null) {
JsonObject website = new JsonObject();
website.addProperty("value", ui.getWebsite());
JsonArray websiteArray = new JsonArray();
websiteArray.add(website);
entry.add("urls", websiteArray);
}
if(ui.getAddress() != null) {
JsonObject addr = new JsonObject();
addr.addProperty("formatted", ui.getAddress().getFormatted());
addr.addProperty("streetAddress", ui.getAddress().getStreetAddress());
addr.addProperty("locality", ui.getAddress().getLocality());
addr.addProperty("region", ui.getAddress().getRegion());
addr.addProperty("postalCode", ui.getAddress().getPostalCode());
addr.addProperty("country", ui.getAddress().getCountry());
JsonArray addrArray = new JsonArray();
addrArray.add(addr);
entry.add("addresses", addrArray);
}
entry.addProperty("updated", ui.getUpdatedTime());
JsonArray entryArray = new JsonArray();
entryArray.add(entry);
poco.add("entry", entryArray);
return poco;
}
}

View File

@ -25,6 +25,9 @@ import org.mitre.openid.connect.exception.InvalidJwtSignatureException;
import org.mitre.openid.connect.model.IdToken;
import org.mitre.util.Utility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@ -39,12 +42,15 @@ public class CheckIDEndpoint {
@Autowired
private ConfigurationPropertiesBean configBean;
@PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping("/checkid")
public ModelAndView checkID(@RequestParam("access_token") String tokenString, ModelAndView mav, HttpServletRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!jwtSignerService.validateSignature(tokenString)) {
// can't validate
throw new InvalidJwtSignatureException(); // TODO: attach a view to this exception
throw new InvalidJwtSignatureException("The Signature could not be validated.");
}
// it's a valid signature, parse the token
@ -53,12 +59,12 @@ public class CheckIDEndpoint {
// check the expiration
if (jwtSignerService.isJwtExpired(token)) {
// token has expired
throw new ExpiredTokenException(); // TODO create a view for this exception
throw new ExpiredTokenException("The token has expired.");
}
// check the issuer (sanity check)
if (!jwtSignerService.validateIssuedJwt(token, configBean.getIssuer())) {
throw new InvalidJwtIssuerException(); // TODO: create a view for this exception
throw new InvalidJwtIssuerException("The JWT issuer is invalid.");
}
// pass the claims directly (the view doesn't care about other fields)

View File

@ -27,6 +27,10 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
@Controller
public class JsonWebKeyEndpoint {
@ -36,14 +40,16 @@ public class JsonWebKeyEndpoint {
@RequestMapping("/jwk")
public ModelAndView getJwk() {
Collection<PublicKey> keys = jwtService.getAllPublicKeys().values();
// get all public keys for display
// map from key id to public key for that signer
Map<String, PublicKey> keys = jwtService.getAllPublicKeys();
// put them into a bidirectional map to get at key IDs
BiMap<String, PublicKey> biKeys = HashBiMap.create(keys);
// TODO: check if keys are empty, return a 404 here or just an empty list?
Map<String, Object> jwk = new HashMap<String, Object>();
jwk.put("jwk", keys);
return new ModelAndView("jwkKeyList", "entity", jwk);
return new ModelAndView("jwkKeyList", "keys", biKeys);
}
}

View File

@ -15,12 +15,16 @@
******************************************************************************/
package org.mitre.openid.connect.web;
import java.security.Principal;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.oauth2.service.OAuth2TokenEntityService;
import org.mitre.openid.connect.exception.UnknownUserInfoSchemaException;
import org.mitre.openid.connect.model.UserInfo;
import org.mitre.openid.connect.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@ -42,6 +46,12 @@ public class UserInfoEndpoint {
@Autowired
UserInfoService userInfoService;
// Valid schemas and associated views
private static final String openIdSchema = "openId";
private static final String pocoSchema = "poco";
private static final String jsonUserInfoViewName = "jsonUserInfoView";
private static final String pocoUserInfoViewName = "pocoUserInfoView";
/**
* Get information about the user as specified in the accessToken->idToken included in this request
*
@ -51,34 +61,25 @@ public class UserInfoEndpoint {
* @return JSON or JWT response containing UserInfo data
*/
@RequestMapping(value="/userinfo", method= {RequestMethod.GET, RequestMethod.POST})
public ModelAndView getInfo(@RequestParam("access_token") String accessToken, @RequestParam("schema") String schema, ModelAndView mav) {
public ModelAndView getInfo(Principal p, @RequestParam("schema") String schema, ModelAndView mav) {
//This will throw the proper error if the token cannot be found
OAuth2AccessTokenEntity token = tokenService.getAccessToken(accessToken);
if (schema != "openid") {
//openid is the ONLY defined schema and is a required parameter
//Will we be defining other schemas?
//if schema is unrecognized, throw an error?
if (p == null) {
throw new UsernameNotFoundException("Invalid User");
}
String userId = token.getIdToken().getTokenClaims().getUserId();
String viewName = null;
if (schema.equalsIgnoreCase( openIdSchema )){
viewName = jsonUserInfoViewName;
} else if (schema.equalsIgnoreCase( pocoSchema )) {
viewName = pocoUserInfoViewName;
} else {
throw new UnknownUserInfoSchemaException("Unknown User Info Schema: " + schema );
}
String userId = p.getName();
UserInfo userInfo = userInfoService.getByUserId(userId);
ClientDetailsEntity client = token.getClient();
//if client wants plain JSON, give it JSON; if it wants a JWT, give it a JWT
//If returning JSON
return new ModelAndView("jsonUserInfoView", "userInfo", userInfo);
// If returning JWT
//Jwt jwt = new Jwt(new JwtHeader(), new JwtClaims(userInfo.toJson()), null);
//sign jwt according to client's userinfo_signed_response_algs parameter
//mav.addObject(jwt);
//return mav;
return new ModelAndView(viewName, "userInfo", userInfo);
}
}

View File

@ -0,0 +1,90 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
/**
*
*/
package org.mitre.swd.view;
import java.io.Writer;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.web.servlet.view.AbstractView;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
/**
* @author jricher
*
*/
public class XrdJsonResponse extends AbstractView {
/* (non-Javadoc)
* @see org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel(java.util.Map, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return false;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
// skip the JPA binding wrapper
if (clazz.equals(BeanPropertyBindingResult.class)) {
return true;
} else {
return false;
}
}
})
.create();
response.setContentType("application/json");
Writer out = response.getWriter();
Map<String, String> links = (Map<String, String>) model.get("links");
JsonObject obj = new JsonObject();
JsonArray linksList = new JsonArray();
obj.add("links", linksList);
// map of "rel" -> "link" values
for (Map.Entry<String, String> link : links.entrySet()) {
JsonObject l = new JsonObject();
l.addProperty("rel", link.getKey());
l.addProperty("link", link.getValue());
linksList.add(l);
}
gson.toJson(obj, out);
}
}

View File

@ -20,7 +20,9 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.mitre.util.Utility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@ -31,11 +33,14 @@ import com.google.common.collect.Lists;
@Controller
public class SimpleWebDiscoveryEndpoint {
@Autowired
ConfigurationPropertiesBean config;
@RequestMapping(value="/.well-known/simple-web-discovery",
params={"principal", "service=http://openid.net/specs/connect/1.0/issuer"})
public ModelAndView openIdConnectIssuerDiscovery(@RequestParam("principal") String principal, ModelAndView modelAndView, HttpServletRequest request) {
public ModelAndView openIdConnectIssuerDiscovery(@RequestParam("principal") String principal, ModelAndView modelAndView) {
String baseUrl = Utility.findBaseUrl(request);
String baseUrl = config.getIssuer();
// look up user, see if they're local
// if so, return this server
@ -51,15 +56,26 @@ public class SimpleWebDiscoveryEndpoint {
return modelAndView;
}
@RequestMapping(value="/.well-known/host-meta",
params={"resource", "rel=http://openid.net/specs/connect/1.0/issuer"})
public ModelAndView xrdDiscovery(@RequestParam("resource") String resource, ModelAndView modelAndView) {
Map<String, String> relMap = new HashMap<String, String>();
relMap.put("http://openid.net/specs/connect/1.0/issuer", config.getIssuer());
modelAndView.getModel().put("links", relMap);
modelAndView.setViewName("jsonXrdResponseView");
return modelAndView;
}
@RequestMapping("/.well-known/openid-configuration")
public ModelAndView providerConfiguration(ModelAndView modelAndView, HttpServletRequest request) {
public ModelAndView providerConfiguration(ModelAndView modelAndView) {
String baseUrl = Utility.findBaseUrl(request);
/*
* version string Version of the provider response. "3.0" is the default.
* issuer string The https: URL with no path component that the OP asserts as its Issuer Identifier
* issuer string The https: URL that the OP asserts as its Issuer Identifier
* authorization_endpoint string URL of the OP's Authentication and Authorization Endpoint [OpenID.Messages]
* token_endpoint string URL of the OP's OAuth 2.0 Token Endpoint [OpenID.Messages]
* userinfo_endpoint string URL of the OP's UserInfo Endpoint [OpenID.Messages]
@ -81,18 +97,24 @@ public class SimpleWebDiscoveryEndpoint {
* token_endpoint_auth_types_supported array A JSON array containing a list of authentication types supported by this Token Endpoint. The options are client_secret_post, client_secret_basic, client_secret_jwt, and private_key_jwt, as described in Section 2.2.1 of OpenID Connect Messages 1.0 [OpenID.Messages]. Other Authentication types may be defined by extension. If unspecified or omitted, the default is client_secret_basic HTTP Basic Authentication Scheme as specified in section 2.3.1 of OAuth 2.0 [OAuth2.0].
* token_endpoint_auth_algs_supported array A JSON array containing a list of the JWS [JWS] signing algorithms supported by the Token Endpoint for the private_key_jwt method to encode the JWT [JWT]. Servers SHOULD support RS256.
*/
String baseUrl = config.getIssuer();
if (!baseUrl.endsWith("/")) {
baseUrl = baseUrl.concat("/");
}
Map<String, Object> m = new HashMap<String, Object>();
m.put("version", "3.0");
m.put("issuer", baseUrl);
m.put("authorization_endpoint", baseUrl + "/authorize");
m.put("token_endpoint", baseUrl + "/oauth");
m.put("userinfo_endpoint", baseUrl + "/userinfo");
m.put("check_id_endpoint", baseUrl + "/checkid");
m.put("refresh_session_endpoint", baseUrl + "/refresh_session");
m.put("end_session_endpoint", baseUrl + "/end_session");
m.put("jwk_url", baseUrl + "/jwk");
m.put("registration_endpoint", baseUrl + "/register_client");
m.put("scopes_supported", Lists.newArrayList("openid"));
m.put("issuer", config.getIssuer());
m.put("authorization_endpoint", baseUrl + "openidconnect/auth");
m.put("token_endpoint", baseUrl + "openidconnect/token");
m.put("userinfo_endpoint", baseUrl + "userinfo");
m.put("check_id_endpoint", baseUrl + "checkid");
//m.put("refresh_session_endpoint", baseUrl + "/refresh_session");
//m.put("end_session_endpoint", baseUrl + "/end_session");
m.put("jwk_url", baseUrl + "jwk");
//m.put("registration_endpoint", baseUrl + "/register_client");
m.put("scopes_supported", Lists.newArrayList("openid", "email", "profile", "address", "phone"));
m.put("response_types_supported", Lists.newArrayList("code"));

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd
@ -16,8 +16,9 @@
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- Scan for components -->
<context:component-scan annotation-config="true" base-package="org.mitre"/>
<context:component-scan annotation-config="true"
base-package="org.mitre" />
<!-- Enables the Spring MVC @Controller programming model -->
<tx:annotation-driven transaction-manager="transactionManager" />
<mvc:annotation-driven />
@ -27,20 +28,29 @@
<import resource="server-config.xml" />
<!-- Import the data context -->
<import resource="data-context.xml" />
<import resource="data-context.xml" />
<!-- Spring Security configuration -->
<security:http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="clientAuthenticationManager"
<!-- Spring Security configuration -->
<oauth:resource-server id="resourceServerFilter"
token-services-ref="defaultOAuth2ProviderTokenService" />
<security:http pattern="/oauth/token" create-session="stateless"
authentication-manager-ref="clientAuthenticationManager"
entry-point-ref="oauthAuthenticationEntryPoint">
<security:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
<security:intercept-url pattern="/oauth/token"
access="IS_AUTHENTICATED_FULLY" />
<security:anonymous enabled="false" />
<security:http-basic entry-point-ref="oauthAuthenticationEntryPoint" />
<!-- include this only if you need to authenticate clients via request parameters -->
<security:custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" />
<!-- include this only if you need to authenticate clients via request
parameters -->
<security:custom-filter ref="clientCredentialsTokenEndpointFilter"
before="BASIC_AUTH_FILTER" />
<security:access-denied-handler ref="oauthAccessDeniedHandler" />
</security:http>
<bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<bean id="oauthAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="openidconnect" />
</bean>
@ -58,37 +68,46 @@
<oauth:authorization-code authorization-code-services-ref="authCodeServices" />
</oauth:authorization-server>
<bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
<bean id="oauthAccessDeniedHandler"
class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
<bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<bean id="clientCredentialsTokenEndpointFilter"
class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<property name="authenticationManager" ref="clientAuthenticationManager" />
</bean>
<authentication-manager id="clientAuthenticationManager" xmlns="http://www.springframework.org/schema/security">
<authentication-manager id="clientAuthenticationManager"
xmlns="http://www.springframework.org/schema/security">
<authentication-provider user-service-ref="clientUserDetailsService" />
</authentication-manager>
<bean id="authorizationRequestFactory" class="org.springframework.security.oauth2.provider.DefaultAuthorizationRequestFactory">
<constructor-arg>
<bean class="org.mitre.oauth2.service.impl.DefaultOAuth2ClientDetailsEntityService"/>
<bean
class="org.mitre.oauth2.service.impl.DefaultOAuth2ClientDetailsEntityService" />
</constructor-arg>
</bean>
<bean class="org.springframework.security.oauth2.provider.approval.TokenServicesUserApprovalHandler" id="userApprovalHandler">
<property name="tokenServices" ref="defaultOAuth2ProviderTokenService"/>
</bean>
<bean id="authCodeServices" class="org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices"/>
<bean
class="org.springframework.security.oauth2.provider.approval.TokenServicesUserApprovalHandler"
id="userApprovalHandler">
<property name="tokenServices" ref="defaultOAuth2ProviderTokenService" />
</bean>
<bean id="authCodeServices"
class="org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices" />
<!-- user services -->
<import resource="user-context.xml" />
<!-- End Spring Security configuration -->
<!-- JPA -->
<bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
<property name="databasePlatform" value="org.eclipse.persistence.platform.database.MySQLPlatform" />
<!-- End Spring Security configuration -->
<!-- JPA -->
<bean id="jpaAdapter"
class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
<property name="databasePlatform"
value="org.eclipse.persistence.platform.database.MySQLPlatform" />
<property name="showSql" value="true" />
</bean>
@ -100,7 +119,7 @@
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="openidPersistenceUnit" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="jpaAdapter" />
<property name="jpaVendorAdapter" ref="jpaAdapter" />
<property name="jpaPropertyMap">
<map>
<entry key="eclipselink.weaving" value="false" />
@ -110,16 +129,17 @@
</property>
</bean>
<!-- End JPA -->
<!-- End JPA -->
<!-- Crypto -->
<!-- Crypto -->
<bean id="defaultKeystore" class="org.mitre.jwt.signer.service.impl.KeyStore">
<constructor-arg name="location" value="classpath:keystore.jks" />
<constructor-arg name="password" value="changeit" />
</bean>
<bean id="defaultsignerService" class="org.mitre.jwt.signer.service.impl.JwtSigningAndValidationServiceDefault">
<bean id="defaultsignerService"
class="org.mitre.jwt.signer.service.impl.JwtSigningAndValidationServiceDefault">
<property name="signers">
<map>
<entry key="rsa1">
@ -137,49 +157,77 @@
</bean>
</entry>
</map>
</property>
</bean>
</property>
</bean>
<!-- End Crypto -->
<!-- End Crypto -->
<!-- View configuration -->
<!-- View configuration -->
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<!-- Handles HTTP GET requests for /resources/** by efficiently serving
up static resources in the ${webappRoot}/resources directory -->
<mvc:resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources
in the /WEB-INF/views directory -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
<property name="order" value="2"/>
<property name="order" value="2" />
</bean>
<!-- Resolve views based on string names -->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" >
<property name="order" value="1"/>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="1" />
</bean>
<!-- Map our custom exception classes to named views -->
<!-- <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> -->
<!-- <property name="exceptionMappings"> -->
<!-- </property> -->
<!-- </bean> -->
<!-- <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> -->
<!-- <property name="exceptionMappings"> -->
<!-- </property> -->
<!-- </bean> -->
<!-- JSON views for each type of model object -->
<bean id="jsonOpenIdConfigurationView" class="org.mitre.swd.view.JsonOpenIdConfigurationView" />
<bean id="jsonSwdResponseView" class="org.mitre.swd.view.SwdResponse" />
<bean id="jwkKeyList" class="org.mitre.openid.connect.view.JwkKeyListView" />
<bean id="jsonXrdResponseView" class="org.mitre.swd.view.XrdJsonResponse" />
<bean id="jsonUserInfoView" class="org.mitre.openid.connect.view.JSONUserInfoView"/>
<bean id="jsonIdTokenView" class="org.mitre.openid.connect.view.JSONIdTokenView"/>
<bean id="jsonClientView" class="org.mitre.openid.connect.view.JSONClientView" />
<bean id="jwkKeyList" class="org.mitre.openid.connect.view.JwkKeyListView" />
<!-- End view configuration -->
<bean id="jsonUserInfoView" class="org.mitre.openid.connect.view.JSONUserInfoView" />
<bean id="pocoUserInfoView" class="org.mitre.openid.connect.view.POCOUserInfoView" />
<bean id="jsonIdTokenView" class="org.mitre.openid.connect.view.JSONIdTokenView" />
<bean id="jsonClientView" class="org.mitre.openid.connect.view.JSONClientView" />
<bean name="exceptionAsJSONView" class="org.mitre.openid.connect.view.ExceptionAsJSONView" />
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.mitre.openid.connect.web.InvalidJwtSignatureException">
exceptionAsJSONView
</prop>
<prop key="org.mitre.openid.connect.web.ExpiredTokenException">
exceptionAsJSONView
</prop>
<prop key="org.mitre.openid.connect.web.InvalidJwtIssuerException">
exceptionAsJSONView
</prop>
</props>
</property>
</bean>
<!-- End view configuration -->
<!-- scheduled tasks -->
<!-- <task:scheduler id="taskScheduler" pool-size="10" /> -->
<!-- <task:executor id="taskExecutor" pool-size="5" /> -->
<!-- <task:annotation-driven scheduler="taskScheduler" executor="taskExecutor" /> -->
<!-- <task:scheduler id="taskScheduler" pool-size="10" /> -->
<!-- <task:executor id="taskExecutor" pool-size="5" /> -->
<!-- <task:annotation-driven scheduler="taskScheduler" executor="taskExecutor"
/> -->
</beans>

View File

@ -32,6 +32,7 @@
<security:http auto-config="true" disable-url-rewriting="true"> <!-- authentication-manager-ref="springSecurityAuthenticationManager" -->
<security:intercept-url pattern="/oauth/**" access="ROLE_USER" />
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<security:custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<security:anonymous />
</security:http>

View File

@ -21,14 +21,56 @@
<authz:authorize ifAnyGranted="ROLE_USER">
<div class="hero-unit" style="text-align:center">
<h1>Please Confirm!</h1>
<div class="well" style="text-align:center">
<h1>Approve New Site</h1>
<p>I hereby authorize "<c:out value="${client.clientId}"/>" to access my protected resources.</p>
<form name="confirmationForm" style="display:inline" action="<%=request.getContextPath()%>/oauth/authorize"
method="post">
<div class="row">
<div class="span4 offset2 well-small" style="text-align:left">Do you authorize
"<c:choose>
<c:when test="${empty client.clientName}">
<c:out value="${client.clientId}"/>
</c:when>
<c:otherwise>
<c:out value="${client.clientName}"/>
</c:otherwise>
</c:choose>" to sign you into their site
using your identity?
<a class="small" href="#" onclick="$('#description').toggle('fast')">more information</a>
<p>
<blockquote id="description" style="display: none">
<c:choose>
<c:when test="${empty client.clientDescription}">
No additional information available.
</c:when>
<c:otherwise>
<c:out value="${client.clientDescription}"/>
</c:otherwise>
</c:choose>
</blockquote>
</p>
</div>
<div class="span4">
<fieldset style="text-align:left" class="well">
<legend style="margin-bottom: 0;">Access to:</legend>
<label for="option1"></label>
<input type="checkbox" name="option1" id="option1" checked="checked"> basic profile information
<label for="option2"></label>
<input type="checkbox" name="option1" id="option2" checked="checked"> email address
<label for="option3"></label>
<input type="checkbox" name="option3" id="option3" checked="checked"> address
<label for="option4"></label>
<input type="checkbox" name="option4" id="option4" checked="checked"> phone number
<label for="option5"></label>
<input type="checkbox" name="option5" id="option5" checked="checked"> offline access
</fieldset>
</div>
</div>
<form name="confirmationForm" style="display:inline" action="<%=request.getContextPath()%>/oauth/authorize" method="post">
<div class="row">
<input id="user_oauth_approval" name="user_oauth_approval" value="true" type="hidden"/>
@ -39,18 +81,8 @@
<input name="deny" value="Deny" type="submit" onclick="$('#user_oauth_approval').attr('value',false)"
class="btn btn-secondary btn-large"/>
</div>
<div class="row control-group">
<label for="option1"></label>
<input name="option1" id="option1" type="checkbox"> Check me out
<label for="option2"></label>
<input name="option2" id="option2" type="checkbox"> Check me out
</div>
</form>
<div>
<a href="#" class="small">learn more</a>
</div>
</authz:authorize>

View File

@ -18,15 +18,15 @@
validate:{
clientName:{
required:true,
/* required:true,
pattern:/^[\w ]+$/,
minlength:3,
minlength:3,*/
maxlength:100
},
clientDescription:{
required:true,
/*required:true,
pattern:/^[\w ]+$/,
minlength:3,
minlength:3,*/
maxlength:200
},
accessTokenTimeout: {
@ -44,14 +44,15 @@
validateURI: function(attributeName, attributeValue) {
var expression = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi;
var expression = /^(?:([a-z0-9+.-]+:\/\/)((?:(?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*)@)?((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*)(:(?:\d*))?(\/(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*)?|([a-z0-9+.-]+:)(\/?(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9A-F]{2})+(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*)?)(\?(?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*)?(#(?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*)?$/i;
var regex = new RegExp(expression);
if (!attributeValue.every(function (url) {
if (url.match(regex)) {
return true;
for (var i in attributeValue) {
if (!attributeValue[i].match(regex)) {
return "Invalid URI";
}
})) return "Invalid URI";
}
},
@ -59,9 +60,10 @@
// We can pass it default values.
defaults:{
clientName:"",
clientSecret:"",
registeredRedirectUri:[""],
authorizedGrantTypes:[],
scope:[""],
scope:["openid"],
authorities:[],
clientDescription:"",
clientId:null,
@ -114,19 +116,22 @@
deleteClient:function () {
var self = this;
if (confirm("Are you sure sure you would like to delete this client?")) {
var self = this;
this.model.destroy({
success:function () {
self.$el.fadeTo("fast", 0.00, function(){ //fade
$(this).slideUp("fast", function() { //slide up
$(this).remove(); //then remove from the DOM
this.model.destroy({
success:function () {
self.$el.fadeTo("fast", 0.00, function () { //fade
$(this).slideUp("fast", function () { //slide up
$(this).remove(); //then remove from the DOM
});
});
});
}
});
}
});
app.clientListView.delegateEvents();
}
app.clientListView.delegateEvents();
return false;
},
@ -185,8 +190,9 @@
$('.control-group').removeClass('error');
this.model.set({
var valid = this.model.set({
clientName:$('#clientName input').val(),
clientSecret:$('#clientSecret input').val(),
registeredRedirectUri:$.trim($('#registeredRedirectUri textarea').val()).replace(/ /g,'').split("\n"),
clientDescription:$('#clientDescription textarea').val(),
allowRefresh:$('#allowRefresh').is(':checked'),
@ -195,26 +201,28 @@
scope:$.map($('#scope textarea').val().replace(/,$/,'').replace(/\s/g,' ').split(","), $.trim)
});
this.model.save(this.model, {
success:function () {
app.navigate('clients', {trigger: true});
},
error:function() {
}
});
if (this.model.isNew() && this.model.isValid()) {
var self = this;
app.clientList.create(this.model, {
if (valid) {
this.model.save(this.model, {
success:function () {
app.navigate('clients', {trigger: true});
app.navigate('clients', {trigger:true});
},
error:function() {
error:function () {
}
});
if (this.model.isNew()) {
var self = this;
app.clientList.create(this.model, {
success:function () {
app.navigate('clients', {trigger:true});
},
error:function () {
}
});
}
}
return false;

View File

@ -1,4 +1,7 @@
<script type="text/html" id="tmpl-client">
<td>
<%=clientId%>
</td>
<td>
<%=clientName%>
@ -30,9 +33,7 @@
<% } %>
</ul>
</td>
<td>
<%=authorities[0]%>
</td>
<td>
<%=clientDescription%>
</td>
@ -57,11 +58,11 @@
<table id="client-table" class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Redirect URI(s)</th>
<th>Grant Types</th>
<th>Scope</th>
<th>Authority</th>
<th>Description</th>
<th>Refresh Tokens</th>
<th class="span1"></th>
@ -86,7 +87,7 @@
<div class="">
<form>
<fieldset>
<legend>Details</legend>
<legend>Details <%=(clientId != null ? 'for ' + clientId : '')%></legend>
<div class="well">
@ -94,20 +95,22 @@
<div class="span6">
<span class="control-group" id="clientName">
<label>Client name</label>
<input value="<%=clientName%>" type="text" class="" placeholder="Type something"> <span class="help-inline">This must be more than three characters and can only be alpha-numeric </span>
<input value="<%=clientName%>" maxlength="100" type="text" class="" placeholder="Type something"> <span class="help-inline"></span>
</span>
<span class="control-group" id="registeredRedirectUri">
<label>Redirect URI(s)</label>
<textarea class="input-xlarge" placeholder="http://"
rows="3"><% for (var i in registeredRedirectUri) { %><%=registeredRedirectUri[i]+"\n"%><% } %></textarea> <span class="help-inline">You may enter multiple URLs separated by a new lines</span>
rows="3"><% for (var i in registeredRedirectUri) { %><%=registeredRedirectUri[i]+"\n"%><% } %></textarea> <span class="help-inline">You may enter multiple URIs separated by a new lines</span>
</span>
</div>
<div class="span6">
<span class="control-group" id="clientDescription">
<label>Description</label>
<textarea class="input-xlarge" placeholder="Type a description"
rows="3"><%=clientDescription%></textarea> <span class="help-inline">This must be more than three characters and can only be alpha-numeric</span>
<textarea class="input-xlarge" placeholder="Type a description" maxlength="200"
rows="3"><%=clientDescription%></textarea> <span class="help-inline"></span>
</span>
</div>
</div>
@ -129,51 +132,23 @@
<div class="row-fluid">
<div class="span4 control-group">
<div class="span4 control-group" id="scope">
<label class="control-label">Grant Types</label>
<label class="control-label">Scope</label>
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="grant-type-checkbox-list1" value="option1">
Option one
</label>
<label class="checkbox">
<input type="checkbox" name="grant-type-checkbox-list2" value="option2">
Option two
</label>
<label class="checkbox">
<input type="checkbox" name="grant-type-checkbox-list3" value="option3">
Option three
</label>
<p class="help-block"><strong>Note:</strong> Labels surround all the options for
much larger click areas and a more usable form.</p>
</div>
<textarea rows="3" class="xlarge" placeholder="openid"
id="textarea2" name="textarea2"><% for (var i in scope) { %><%=scope[i]+","%><% }%></textarea>
<span class="help-block">
Please enter scopes separated by commas
</span>
</div>
<div class="span4 control-group">
<div class="span4 control-group" id="clientSecret">
<label class="control-label">Auth Types</label>
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="auth-type-checkbox-list1" value="option1">
Option one
</label>
<label class="checkbox">
<input type="checkbox" name="auth-type-checkbox-list2" value="option2">
Option two
</label>
<label class="checkbox">
<input type="checkbox" name="auth-type-checkbox-list3" value="option3">
Option three
</label>
<p class="help-block"><strong>Note:</strong> Labels surround all the options for
much larger click areas and a more usable form.</p>
</div>
<label>Client Secret</label>
<input value="<%=clientSecret%>" maxlength="100" type="text" class=""
placeholder="Type a secret"> <span class="help-inline">If you leave this blank a client secret will be generated for you automatically.</span>
</div>
@ -187,7 +162,7 @@
<input type="text" class="" value="<%=accessTokenTimeout%>" id="access-token-timeout-seconds" size="16"><span
class="add-on">seconds</span>
</div>
<span class="help-inline">Here's more help text</span>
<span class="help-inline">Enter this time in seconds</span>
</div>
</span>
<span class="control-group" id="refreshTokenTimeout">
@ -199,25 +174,11 @@
<input type="text" class="" value="<%=refreshTokenTimeout%>" id="refresh-token-timeout-seconds" size="16"><span
class="add-on">seconds</span>
</div>
<span class="help-inline">Here's more help text</span>
<span class="help-inline">Enter this time in seconds</span>
</div>
</span>
</div>
<div id="scope" class="span12 control-group">
<label for="textarea2">Scope</label>
<div class="input">
<textarea rows="3" class="xlarge span10" placeholder="email,first name, last name"
id="textarea2" name="textarea2"><% for (var i in scope) { %><%=scope[i]+","%><% } %></textarea>
<span class="help-block">
Please enter scopes separated by commas
</span>
</div>
</div>
</div>
</fieldset>
</form>

View File

@ -12,19 +12,19 @@
<property name="scriptLocations" >
<list>
<!-- OpenID Connect Data model -->
<value>file:src/main/webapp/db/tables/accesstoken.sql</value>
<value>file:src/main/webapp/db/tables/address.sql</value>
<value>file:src/main/webapp/db/tables/approvedsite.sql</value>
<value>file:src/main/webapp/db/tables/authorities.sql</value>
<value>file:src/main/webapp/db/tables/clientdetails.sql</value>
<value>file:src/main/webapp/db/tables/event.sql</value>
<value>file:src/main/webapp/db/tables/granttypes.sql</value>
<value>file:src/main/webapp/db/tables/idtoken.sql</value>
<value>file:src/main/webapp/db/tables/idtokenclaims.sql</value>
<value>file:src/main/webapp/db/tables/refreshtoken.sql</value>
<value>file:src/main/webapp/db/tables/scope.sql</value>
<value>file:src/main/webapp/db/tables/userinfo.sql</value>
<value>file:src/main/webapp/db/tables/whitelistedsite.sql</value>
<value>file:db/tables/accesstoken.sql</value>
<value>file:db/tables/address.sql</value>
<value>file:db/tables/approvedsite.sql</value>
<value>file:db/tables/authorities.sql</value>
<value>file:db/tables/clientdetails.sql</value>
<value>file:db/tables/event.sql</value>
<value>file:db/tables/granttypes.sql</value>
<value>file:db/tables/idtoken.sql</value>
<value>file:db/tables/idtokenclaims.sql</value>
<value>file:db/tables/refreshtoken.sql</value>
<value>file:db/tables/scope.sql</value>
<value>file:db/tables/userinfo.sql</value>
<value>file:db/tables/whitelistedsite.sql</value>
<!-- Preloaded data -->
<value>classpath:test-data.sql</value>
</list>