Merge branch 'master' into userInfoEndpoint
commit
5c544dfe7c
|
@ -2,53 +2,60 @@
|
|||
|
||||
## Overview
|
||||
|
||||
This is Web application created in response to [Issue #39] to permit the Client AuthenticationFilter to speak to multiple OpenID Connect servers.
|
||||
This is Web application created in response to [Issue #39] to permit the Client AuthenticationFilter to speak to multiple OpenID Connect Servers. The protocol between the Clinent and the Account Chooser UI application is documented the README.md of the openid-connect-client submodule.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure a bean configuration to the spring-servlet.xml like so:
|
||||
Configure AccountChooserController via configuring a AccountChooserConfig bean in the spring-servlet.xml like so:
|
||||
|
||||
<bean class="org.mitre.account_chooser.OIDCServers">
|
||||
<property name="servers">
|
||||
<bean name="AccountChooserConfig" class="org.mitre.account_chooser.AccountChooserConfig">
|
||||
<property name="issuers">
|
||||
<map>
|
||||
<entry key="1">
|
||||
<entry key="http://sever.example.com:8080/openid-connect-server">
|
||||
<bean class="org.mitre.account_chooser.OIDCServer">
|
||||
<property name="name" value="OIDC Server 1" />
|
||||
</bean>
|
||||
</entry>
|
||||
<entry key="2">
|
||||
<bean class="org.mitre.account_chooser.OIDCServer">
|
||||
<property name="name" value="OIDC Server 2" />
|
||||
</bean>
|
||||
</entry>
|
||||
<entry key="3">
|
||||
<bean class="org.mitre.account_chooser.OIDCServer">
|
||||
<property name="name" value="OIDC Server 3" />
|
||||
<property name="name" value="Example Server" />
|
||||
</bean>
|
||||
</entry>
|
||||
</map>
|
||||
</property>
|
||||
<property name="validClientIds" value="FGWEUIASJK, IUYTTYEV, GFHDSFYD" />
|
||||
</bean>
|
||||
|
||||
|
||||
|
||||
The keys must match those found in the OpenIdConnectAuthenticationFilter's configuration like so:
|
||||
|
||||
<bean id="openIdConnectAuthenticationFilter"
|
||||
class="org.mitre.openid.connect.client.OpenIdConnectAuthenticationFilter">
|
||||
<property name="OIDCServers">
|
||||
<property name="errorRedirectURI" value="/login.jsp?authfail=openid" />
|
||||
<property name="authenticationManager" ref="authenticationManager" />
|
||||
<property name="accountChooserURI"
|
||||
value="http://sever.example.com:8080/account-chooser" />
|
||||
<property name="accountChooserClientID" value="FGWEUIASJK" />
|
||||
<property name="oidcServerConfigs">
|
||||
<map>
|
||||
<entry key="1">
|
||||
<property name="authorizationEndpointURI"
|
||||
value="http://sever.example.com:8080/openid-connect-server/oauth/authorize" />
|
||||
<property name="tokenEndpointURI"
|
||||
value="http://sever.example.com:8080/openid-connect-server/oauth/token" />
|
||||
<property name="checkIDEndpointURI"
|
||||
value="http://sever.example.com:8080/openid-connect-server/checkid" />
|
||||
<property name="clientId"
|
||||
value="someClientId" />
|
||||
<property name="clientSecret" value="someClientSecret" />
|
||||
<entry key="http://sever.example.com:8080/Fopenid-connect-server">
|
||||
<bean class="org.mitre.openid.connect.client.OIDCServerConfiguration">
|
||||
<property name="authorizationEndpointURI"
|
||||
value="http://sever.example.com:8080/openid-connect-server/oauth/authorize" />
|
||||
<property name="tokenEndpointURI"
|
||||
value="http://sever.example.com:8080/openid-connect-server/oauth/token" />
|
||||
<property name="checkIDEndpointURI"
|
||||
value="http://sever.example.com:8080/openid-connect-server/checkid" />
|
||||
<property name="clientId"
|
||||
value="someClientId" />
|
||||
<property name="clientSecret" value="someClientSecret" />
|
||||
</bean>
|
||||
</entry>
|
||||
…
|
||||
. . .
|
||||
|
||||
[Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client"
|
||||
|
||||
## Test the Default Configuration
|
||||
|
||||
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 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"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
protocol.md
|
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
|
@ -0,0 +1,127 @@
|
|||
# OpenID Connect Client - Account Chooser UI Application Protocol
|
||||
|
||||
## Overview
|
||||
|
||||
This Client - Account Chooser Protocol proposed in response to [Issue #39].
|
||||
|
||||
|
||||
#### Authorization when using Account Chooser Code Flow
|
||||
|
||||
The Authorization when using Account Chooser Code Flow goes through the following steps:
|
||||
|
||||
1. Client prepares an Account Chooser Request containing the desired request parameters.
|
||||
2. Client sends a request to the Account Chooser.
|
||||
3. Account Chooser presents a selection of OpenID Connect (OIDC) Servers from which the End-User must select from.
|
||||
4. End-User selects an OIDC.
|
||||
5. Account Chooser Sends the End-User back to the Client with key value of the OIDC End-User selected.
|
||||
6. The Client begins the Authorization flow described in [Authorization Code Flow][OpenID Connect Standard] of the [OpenID Connect Standard].
|
||||
|
||||
#### Account Chooser Request
|
||||
|
||||
When the End-User wishes to access a Protected Resource and the End-User Authorization has not yet been obtained, the Client will redirect the End-User to Account Chooser.
|
||||
|
||||
Account Chooser MUST support the use of the HTTP "GET" and "POST" methods defined in RFC 2616 [RFC2616].
|
||||
|
||||
Clients MAY use the HTTP "GET" or "POST" method to send the Account Chooser Request to the Account Chooser. If using the HTTP "GET" method, the request parameters are serialized using URI query string serialization. If using the HTTP "POST" method, the request parameters are serialized using form serialization.
|
||||
|
||||
#### Client Prepares an Account Chooser Request
|
||||
|
||||
The Client prepares an Account Chooser Request to the Account Chooser with the request parameters using the HTTP "GET" or "POST" method.
|
||||
|
||||
The required Account Chooser Request parameters are as follows:
|
||||
|
||||
* redirect_uri - REQUIRED. A redirection URI where the response will be sent.
|
||||
* client_id - REQUIRED. An identifier used to identify the Client to the Account Chooser UI application.
|
||||
|
||||
There is one method to construct and send the request to the Account Chooser:
|
||||
|
||||
* Simple Request Method
|
||||
|
||||
#### Simple Request Method
|
||||
|
||||
The Client prepares an Account Chooser Request to the Account Chooser using the appropriate parameters. If using the HTTP "GET" method, the request parameters are serialized using URI query string serialization. If using the HTTP "POST" method, the request parameters are serialized using form serialization.
|
||||
|
||||
The following is a non-normative example of an Account Chooser Request URL. Line wraps are for display purposes only.
|
||||
|
||||
http://server.example.com/chooser?
|
||||
redirect_uri=https%3A%2F%2Fclient.example.com%2Fopenid_connect_login
|
||||
&client_id=FGWEUIASJK
|
||||
|
||||
#### Client sends a request to the Account Chooser
|
||||
|
||||
Having constructed the Account Chooser Request, the Client sends it to the Account Chooser. This MAY happen via redirect, hyperlinking, or any other means of directing the User-Agent to the Account Chooser URL.
|
||||
|
||||
Following is a non-normative example using HTTP redirect. Line wraps are for display purposes only.
|
||||
|
||||
HTTP/1.1 302 Found
|
||||
Location: https://server.example.com/chooser?
|
||||
redirect_uri=https%3A%2F%2Fclient.example.com%2Fopenid_connect_login
|
||||
&client_id=FGWEUIASJK
|
||||
|
||||
#### Account Chooser Sends the End-User back to the Client
|
||||
|
||||
After the End-User has select an OpenID Connect Server, the Client issues an Account Chooser Response and delivers it to the Client by adding the response parameters to redirect_uri specified in the Account Choose Request using the "application/x-www-form-urlencoded" format.
|
||||
|
||||
The following response parameters are included:
|
||||
|
||||
* issuer - REQUIRED. The [Issuer Identifier] describing the selected account.
|
||||
|
||||
The following is non-normative example of a responses. Line wraps are for display purposes only.
|
||||
|
||||
HTTP/1.1 302 Found
|
||||
Location: https://client.example.com/openid_connect_login?
|
||||
issuer=http%3A%2F%2Fsever.example.com%3A8080%2Fopenid-connect-server
|
||||
|
||||
A sequence diagram of a successful interaction would be:
|
||||
|
||||

|
||||
|
||||
#### The Client ID is not Supported by the Account Chooser Application
|
||||
|
||||
If the Client sends a Client identifier that is not supported by the Account Chooser UI application, the Account Chooser MUST return an error response back to the End-User by issuing a HTTP Response with a message concerning the error. The Account Chooser MUST NOT directly return to the Client via the redirection URI specified in the Account Chooser Request automatically.
|
||||
|
||||
The error message the Account Chooser presents to the End-User MAY give the End-User the means to return the indication of an error to the Client, e.g., via the End-User clicking on a HTML link built from adding the error parameters to the query component of the redirection URI. No other parameters SHOULD be returned.
|
||||
|
||||
The error response parameters are the following:
|
||||
|
||||
* error - REQUIRED. The error code.
|
||||
* error_description - OPTIONAL. A human-readable UTF-8 encoded text description of the error.
|
||||
|
||||
The response parameters are added to the query component of the redirection URI specified in the Account Chooser Request.
|
||||
|
||||
The following is a non-normative example. Line wraps after the second line are for the display purposes only.
|
||||
|
||||
https://client.example.com/openid_connect_login?
|
||||
error=not_supported
|
||||
&error_description=The%20client_id%20is%20not%20supported%20by%20the%20AccountChooser%20UI%20application.
|
||||
|
||||
A sequence diagram of the interaction where the Account Chooser was not configured to support the Client would be:
|
||||
|
||||

|
||||
|
||||
#### End-User refuses to select an Account
|
||||
|
||||
If the End-User refuses to select an Account, the Account Chooser MUST return an error response. The Account Chooser returns the Client via the redirection URI specified in the Account Chooser Request with the appropriate error parameters. No other parameters SHOULD be returned.
|
||||
|
||||
The error response parameters are the following:
|
||||
|
||||
* error - REQUIRED. The error code.
|
||||
* error_description - OPTIONAL. A human-readable UTF-8 encoded text description of the error.
|
||||
|
||||
The response parameters are added to the query component of the redirection URI specified in the Account Chooser Request.
|
||||
|
||||
The following is a non-normative example. Line wraps after the second line are for the display purposes only.
|
||||
|
||||
HTTP/1.1 302 Found
|
||||
Location: https://client.example.com/openid_connect_login?
|
||||
error=end_user_cancelled
|
||||
&error_description=The%20end%2Duser%20refused%20to%20select%20an%20Account.
|
||||
|
||||
A sequence diagram of an interaction where the End-User refused to select an Account would be:
|
||||
|
||||

|
||||
|
||||
[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"
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
|
@ -0,0 +1,50 @@
|
|||
Text used to create sequence diagrams @ http://www.websequencediagrams.com/
|
||||
|
||||
|
||||
A Successful Client and Account Chooser Interaction
|
||||
|
||||
Client->End-User Browser: HTTP-Redirect\n to Account\n Chooser URL\nwith redirect_uri,\n and client_id\n parameters
|
||||
activate End-User Browser
|
||||
End-User Browser->Account Chooser:HTTP Get/Put\n Request containing\n redirect_uri\n and client_id
|
||||
note right of "Account Chooser": Client Id\n is verified as\n supported, and the\n End-User selects\n an Account
|
||||
deactivate End-User Browser
|
||||
activate Account Chooser
|
||||
Account Chooser->End-User Browser:HTTP-Redirect to \nAccount Chooser URL\n with issuer parameters
|
||||
activate End-User Browser
|
||||
deactivate Account Chooser
|
||||
End-User Browser->Client: HTTP Get\n Request containing the\n issuer parameter
|
||||
activate Client
|
||||
deactivate End-User Browser
|
||||
|
||||
|
||||
The Client ID is not Supported by the Account Chooser Application
|
||||
|
||||
Client->End-User Browser: HTTP-Redirect to\n Account Chooser URL\n w/ redirect_uri,\n and client_id parameters
|
||||
activate End-User Browser
|
||||
End-User Browser->Account Chooser:HTTP Get/Put\n Request containing\n redirect_uri and client_id
|
||||
note right of "Account Chooser": Client Id is\nnot supported.\nSo, an error must\nbe returned.
|
||||
activate Account Chooser
|
||||
deactivate End-User Browser
|
||||
Account Chooser->End-User Browser:Returns an\nHTTP Response\ncontaining an\nerror message.
|
||||
deactivate Account Chooser
|
||||
note right of "End-User Browser":The End-User is presented\nan error message\nthus ending the interaction.\n\nOptionally, the message\nMAY contain a HTML link\nto return the End-User\nback to the Client with\nan error and error description\nadded to the query component\nof the redirect_uri.
|
||||
activate End-User Browser
|
||||
End-User Browser->Client: If the End-User\nis presented and clicks\non a HTML link,\na HTTP "GET"\n Request containing the\nerror, and error_description\nparameter
|
||||
activate Client
|
||||
deactivate Account Chooser
|
||||
|
||||
|
||||
End-User Cancels Account Selection
|
||||
|
||||
Client->End-User Browser: HTTP-Redirect to\n Account Chooser URL\nwith redirect_uri,\nand client_id parameters
|
||||
activate End-User Browser
|
||||
End-User Browser->Account Chooser:HTTP Get/Put\n Request containing\nredirect_uri and client_id
|
||||
note right of "Account Chooser": End-User refuses\nto select an\naccount via\ncancelling.
|
||||
deactivate End-User Browser
|
||||
activate Account Chooser
|
||||
Account Chooser->End-User Browser:HTTP-Redirect\n to Account Chooser\n URL with error,\nand error_description\nparameters
|
||||
activate End-User Browser
|
||||
deactivate Account Chooser
|
||||
End-User Browser->Client: HTTP Get Request\ncontaining the error,\nand error_description\nparameter
|
||||
activate Client
|
||||
deactivate End-User Browser
|
|
@ -0,0 +1,49 @@
|
|||
/*******************************************************************************
|
||||
* 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.account_chooser;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Used to the configure AccountChooserController
|
||||
*
|
||||
* @author nemonik
|
||||
*
|
||||
*/
|
||||
public class AccountChooserConfig {
|
||||
|
||||
private String[] validClientIds;
|
||||
|
||||
private Map<String, ? extends OIDCServer> issuers = new HashMap<String, OIDCServer>();
|
||||
|
||||
public Map<String, ? extends OIDCServer> getIssuers() {
|
||||
return issuers;
|
||||
}
|
||||
|
||||
public String[] getValidClientIds() {
|
||||
return validClientIds;
|
||||
}
|
||||
|
||||
public void setIssuers(Map<String, ? extends OIDCServer> issuers) {
|
||||
this.issuers = issuers;
|
||||
}
|
||||
|
||||
public void setValidClientIds(String[] validClientIds) {
|
||||
|
||||
this.validClientIds = validClientIds;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,11 @@
|
|||
package org.mitre.account_chooser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
|
@ -33,14 +38,49 @@ import org.springframework.web.servlet.ModelAndView;
|
|||
*
|
||||
* @author nemonik
|
||||
*
|
||||
* See README.md for configuration.
|
||||
* See README.md for configuration.
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
public class AccountChooserController {
|
||||
|
||||
/**
|
||||
* Return the URL w/ GET parameters
|
||||
*
|
||||
* @param baseURI
|
||||
* A String containing the protocol, server address, path, and
|
||||
* program as per "http://server/path/program"
|
||||
* @param queryStringFields
|
||||
* A map where each key is the field name and the associated
|
||||
* key's value is the field value used to populate the URL's
|
||||
* query string
|
||||
* @return A String representing the URL in form of
|
||||
* http://server/path/program?query_string from the messaged
|
||||
* parameters.
|
||||
*/
|
||||
public static String buildURL(String baseURI,
|
||||
Map<String, String> queryStringFields) {
|
||||
|
||||
StringBuilder URLBuilder = new StringBuilder(baseURI);
|
||||
|
||||
char appendChar = '?';
|
||||
|
||||
for (Map.Entry<String, String> param : queryStringFields.entrySet()) {
|
||||
try {
|
||||
URLBuilder.append(appendChar).append(param.getKey())
|
||||
.append('=')
|
||||
.append(URLEncoder.encode(param.getValue(), "UTF-8"));
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new IllegalStateException(uee);
|
||||
}
|
||||
appendChar = '&';
|
||||
}
|
||||
|
||||
return URLBuilder.toString();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
OIDCServers servers;
|
||||
AccountChooserConfig accountChooserConfig;
|
||||
|
||||
private static Log logger = LogFactory
|
||||
.getLog(AccountChooserController.class);
|
||||
|
@ -51,18 +91,51 @@ public class AccountChooserController {
|
|||
* @param redirectUri
|
||||
* A redirection URI where the response will be sent
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
@RequestMapping(value = "/", method = { RequestMethod.GET,
|
||||
RequestMethod.POST })
|
||||
public ModelAndView handleChooserRequest(
|
||||
@RequestParam("redirect_uri") String redirectUri) {
|
||||
@RequestParam("redirect_uri") String redirectUri,
|
||||
@RequestParam("client_id") String clientId,
|
||||
HttpServletResponse response) throws IOException {
|
||||
|
||||
ModelAndView modelAndView = new ModelAndView("form");
|
||||
modelAndView.addObject("servers", servers);
|
||||
modelAndView.addObject("redirect_uri", redirectUri);
|
||||
modelAndView.setViewName("chooser");
|
||||
ModelAndView modelAndView = null;
|
||||
|
||||
if (Arrays.asList(accountChooserConfig.getValidClientIds()).contains(
|
||||
clientId)) {
|
||||
|
||||
// client_id supported
|
||||
|
||||
modelAndView = new ModelAndView("chooser");
|
||||
modelAndView
|
||||
.addObject("issuers", accountChooserConfig.getIssuers());
|
||||
modelAndView.addObject("redirect_uri", redirectUri);
|
||||
modelAndView.addObject("client_id", clientId);
|
||||
|
||||
} else {
|
||||
|
||||
// client_id not supported
|
||||
|
||||
Map<String, String> urlVariables = new HashMap<String, String>();
|
||||
|
||||
urlVariables.put("error", "not_supported");
|
||||
urlVariables
|
||||
.put("error_description",
|
||||
"The client_id is not supported by the Account Chooser UI application.");
|
||||
|
||||
modelAndView = new ModelAndView("error");
|
||||
|
||||
modelAndView.addObject("error", urlVariables.get("error"));
|
||||
modelAndView.addObject("error_description",
|
||||
urlVariables.get("error_description"));
|
||||
modelAndView.addObject("client_uri", AccountChooserController
|
||||
.buildURL(redirectUri, urlVariables));
|
||||
|
||||
}
|
||||
|
||||
return modelAndView;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,9 +154,50 @@ public class AccountChooserController {
|
|||
*/
|
||||
@RequestMapping(value = "/selected")
|
||||
public void processSubmit(@RequestParam("redirect_uri") String redirectUri,
|
||||
@RequestParam("alias") String alias, HttpServletResponse response)
|
||||
@RequestParam("issuer") String issuer,
|
||||
@RequestParam("client_id") String clientId,
|
||||
HttpServletResponse response)
|
||||
throws IOException {
|
||||
|
||||
response.sendRedirect(redirectUri + "?oidc_alias=" + alias);
|
||||
// Handle Submit
|
||||
|
||||
Map<String, String> urlVariables = new HashMap<String, String>();
|
||||
urlVariables.put("issuer", issuer);
|
||||
|
||||
response.sendRedirect(AccountChooserController.buildURL(redirectUri,
|
||||
urlVariables));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles form submits
|
||||
*
|
||||
* @param redirectUri
|
||||
* A redirection URI where the response will be sent.
|
||||
* @param alias
|
||||
* The OIDC alias selected.
|
||||
* @param response
|
||||
* Provide the HTTP-specific functionality for sending a
|
||||
* response. In this case a redirect to redirect the End-User
|
||||
* back to the OpenID Connect Client.
|
||||
* @throws IOException
|
||||
* If an output exception occurs in sending the redirect.
|
||||
*/
|
||||
@RequestMapping(value = "/cancel")
|
||||
public void processCancel(@RequestParam("redirect_uri") String redirectUri,
|
||||
@RequestParam("issuer") String issuer,
|
||||
@RequestParam("client_id") String clientId,
|
||||
HttpServletResponse response)
|
||||
throws IOException {
|
||||
|
||||
// Handle Cancel
|
||||
|
||||
Map<String, String> urlVariables = new HashMap<String, String>();
|
||||
urlVariables.put("error", "end_user_cancelled");
|
||||
urlVariables.put("error_description",
|
||||
"The end-user refused to select an Account.");
|
||||
|
||||
response.sendRedirect(AccountChooserController.buildURL(redirectUri,
|
||||
urlVariables));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ package org.mitre.account_chooser;
|
|||
/**
|
||||
* @author nemonik
|
||||
*
|
||||
* Holdes properties of the Issuer, e.g. name, icon URL, description, et cetera
|
||||
*
|
||||
*/
|
||||
public class OIDCServer {
|
||||
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* 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.account_chooser;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* @author nemonik
|
||||
*
|
||||
*/
|
||||
public class OIDCServers implements InitializingBean {
|
||||
|
||||
private Map<String, ? extends OIDCServer> servers = new HashMap<String, OIDCServer>();
|
||||
|
||||
private static Log logger = LogFactory.getLog(OIDCServers.class);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// used for debugging...
|
||||
if (!servers.isEmpty()) {
|
||||
logger.info(this.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the OIDCServers associated with this
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, ? extends OIDCServer> getServers() {
|
||||
return servers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the OIDCServers associated with this
|
||||
*
|
||||
* @param signers
|
||||
* List of JwtSigners to associate with this service
|
||||
*/
|
||||
public void setServers(Map<String, ? extends OIDCServer> servers) {
|
||||
this.servers = servers;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OIDCServers [servers=" + servers
|
||||
+ "]";
|
||||
}
|
||||
|
||||
}
|
|
@ -35,26 +35,17 @@
|
|||
|
||||
<!-- End view configuration -->
|
||||
|
||||
<bean class="org.mitre.account_chooser.OIDCServers">
|
||||
<property name="servers">
|
||||
<bean name="AccountChooserConfig" class="org.mitre.account_chooser.AccountChooserConfig">
|
||||
<property name="issuers">
|
||||
<map>
|
||||
<entry key="1">
|
||||
<entry key="http://server.example.com:8080/openid-connect-server">
|
||||
<bean class="org.mitre.account_chooser.OIDCServer">
|
||||
<property name="name" value="OIDC Server 1" />
|
||||
</bean>
|
||||
</entry>
|
||||
<entry key="2">
|
||||
<bean class="org.mitre.account_chooser.OIDCServer">
|
||||
<property name="name" value="OIDC Server 2" />
|
||||
</bean>
|
||||
</entry>
|
||||
<entry key="3">
|
||||
<bean class="org.mitre.account_chooser.OIDCServer">
|
||||
<property name="name" value="OIDC Server 3" />
|
||||
<property name="name" value="Example Server" />
|
||||
</bean>
|
||||
</entry>
|
||||
</map>
|
||||
</property>
|
||||
<property name="validClientIds" value="FGWEUIASJK, IUYTTYEV, GFHDSFYD" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
|
@ -3,9 +3,8 @@
|
|||
<%@page import="org.mitre.account_chooser.OIDCServer"%>
|
||||
<%@page import="java.util.Map"%>
|
||||
<%@page import="java.util.Iterator"%>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
|
||||
<jsp:useBean id="servers" type="org.mitre.account_chooser.OIDCServers"
|
||||
<jsp:useBean id="issuers" type="java.util.Map"
|
||||
scope="request" />
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
@ -36,22 +35,21 @@ body {
|
|||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span12">
|
||||
<form class="form-horizontal" action="selected" method="get">
|
||||
<form name="chooser" class="form-horizontal" action="selected" method="get">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="select01">Account:</label>
|
||||
<div class="controls">
|
||||
<select name="alias">
|
||||
<select name="issuer">
|
||||
<%
|
||||
Map map = servers.getServers();
|
||||
Iterator entries = map.entrySet().iterator();
|
||||
Iterator entries = issuers.entrySet().iterator();
|
||||
|
||||
while (entries.hasNext()) {
|
||||
Map.Entry entry = (Map.Entry) entries.next();
|
||||
|
||||
String alias = (String) entry.getKey();
|
||||
OIDCServer server = (OIDCServer) entry.getValue();
|
||||
String id = (String) entry.getKey();
|
||||
OIDCServer issuer = (OIDCServer) entry.getValue();
|
||||
%>
|
||||
<option value="<%= alias %>"><%= server.getName() %></option>
|
||||
<option value="<%= id %>"><%= issuer.getName() %></option>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
|
@ -61,12 +59,13 @@ body {
|
|||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<input name="redirect_uri" type="hidden" value="<c:out value="${redirect_uri}"/>">
|
||||
<input name="redirect_uri" type="hidden" value="${redirect_uri}">
|
||||
<input name="client_id" type="hidden" value="${client_id}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" type="submit">Submit</button>
|
||||
<button class="btn">Cancel</button>
|
||||
<a class="btn btn-primary" href="javascript: submitForm('selected')">Submit</a>
|
||||
<a class="btn" href="javascript: submitForm('cancel')">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -94,7 +93,10 @@ body {
|
|||
<script type="text/javascript" language="JavaScript">
|
||||
// <![CDATA [
|
||||
|
||||
// javascript secific to the page would go here, if I had any...
|
||||
function submitForm(action){
|
||||
$('.form-horizontal').attr( 'action' , action );
|
||||
$('.form-horizontal').submit();
|
||||
}
|
||||
|
||||
// ]]>
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
pageEncoding="ISO-8859-1"%>
|
||||
<%@page import="org.mitre.account_chooser.OIDCServer"%>
|
||||
<%@page import="java.util.Map"%>
|
||||
<%@page import="java.util.Iterator"%>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Account Chooser</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Account Chooser GUI">
|
||||
<meta name="author" content="nemonik">
|
||||
<link href="resources/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||
<link href="resources/bootstrap/css/bootstrap-responsive.css"
|
||||
rel="stylesheet">
|
||||
<link href="resources/bootstrap/css/docs.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding-top: 60px;
|
||||
/* 60px to make the container go all the way to the bottom of the topbar */
|
||||
}
|
||||
</style>
|
||||
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span12">
|
||||
<h1>
|
||||
${error}<br>
|
||||
<small>${error_description}</small>
|
||||
</h1>
|
||||
|
||||
<a href="${client_uri}">Return to client</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--/container-->
|
||||
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="resources/bootstrap/js/jquery.js"></script>
|
||||
<script src="resources/bootstrap/js/bootstrap-transition.js"></script>
|
||||
<script src="resources/bootstrap/js/bootstrap-alert.js"></script>
|
||||
|
||||
<script src="resources/bootstrap/js/bootstrap-modal.js"></script>
|
||||
<script src="resources/bootstrap/js/bootstrap-dropdown.js"></script>
|
||||
<script src="resources/bootstrap/js/bootstrap-scrollspy.js"></script>
|
||||
<script src="resources/bootstrap/js/bootstrap-tab.js"></script>
|
||||
<script src="resources/bootstrap/js/bootstrap-tooltip.js"></script>
|
||||
<script src="resources/bootstrap/js/bootstrap-popover.js"></script>
|
||||
|
||||
<script src="resources/bootstrap/js/bootstrap-button.js"></script>
|
||||
<script src="resources/bootstrap/js/bootstrap-collapse.js"></script>
|
||||
<script src="resources/bootstrap/js/bootstrap-carousel.js"></script>
|
||||
<script src="resources/bootstrap/js/bootstrap-typeahead.js"></script>
|
||||
|
||||
<script type="text/javascript" language="JavaScript">
|
||||
// <![CDATA [
|
||||
|
||||
// ]]>
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -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>
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>openid-connect-client</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>openid-connect-client</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
|
|
@ -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.
|
||||
|
||||
## Configure
|
||||
## Configuration of OIDCAuthenticationFilter
|
||||
|
||||
Configure the OpenIDConnectAuthenticationFilter 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"
|
||||
|
@ -40,7 +40,7 @@ Configure the OpenIDConnectAuthenticationFilter by adding the XML to your applic
|
|||
<security:authentication-manager alias="authenticationManager" />
|
||||
|
||||
<bean id="openIdConnectAuthenticationProvider"
|
||||
class='org.mitre.openid.connect.client.OpenIdConnectAuthenticationProvider">
|
||||
class='org.mitre.openid.connect.client.OIDCAuthenticationProvider">
|
||||
<property name="userDetaulsService" ref="myUserDetailsService"/>
|
||||
</bean>
|
||||
|
||||
|
@ -63,111 +63,22 @@ Configure the OpenIDConnectAuthenticationFilter by adding the XML to your applic
|
|||
|
||||
You will need to implement your own UserDetailsService and configure as the above does with the reference to *myUserDetailsService*.
|
||||
|
||||
## Proposed Account Chooser UI Application Extension
|
||||
## Configuration of OIDCAuthenticationUsingChooserFilter
|
||||
|
||||
The following proposed extension is in response to [Issue #39].
|
||||
The OIDCAuthenticationUsingChooserFilter was written in response to [Issue #39].
|
||||
|
||||
### Account Chooser Protocol
|
||||
|
||||
The following describes the protocol between the Client and Account Chooser UI application introduced in [Issue #39].
|
||||
|
||||
#### Authorization when using Account Chooser Code Flow
|
||||
|
||||
The Authorization when using Account Chooser Code Flow goes through the following steps.
|
||||
|
||||
1. Client prepares an Account Chooser Request containing the desired request parameters.
|
||||
2. Client sends a request to the Account Chooser.
|
||||
3. Account Chooser presents a selection of OpenID Connect (OIDC) Servers from which the End-User must select from.
|
||||
4. End-User selects an OIDC.
|
||||
5. Account Chooser Sends the End-User back to the Client with key value of the OIDC End-User selected.
|
||||
6. The Client begins the Authorization flow desrcribed in [Authorization Code Flow][OpenID Connect Standard] of the [OpenID Connect Standard].
|
||||
|
||||
#### Account Chooser Request
|
||||
|
||||
When the End-User wishes to access a Protected Resource, and the End-User Authorization has not yet been obtained, the Client will redirect the End-User to Account Chooser.
|
||||
|
||||
Account Chooser MUST support the use of the HTTP "GET" and "POST" methods defined in RFC 2616 [RFC2616].
|
||||
|
||||
Clients MAY use the HTTP "GET" or "POST" method to send the Account Chooser Request to the Account Chooser. If using the HTTP "GET" method, the request parameters are serialized using URI query string serialization. If using the HTTP "POST" method, the request parameters are serialized using form serialization.
|
||||
|
||||
#### Client Prepares an Account Chooser Request
|
||||
|
||||
The Client prepares an Account Chooser Request to the Account Chooser with the request parameters using the HTTP "GET" or "POST" method.
|
||||
|
||||
The required Account Chooser Request parameters are as follows:
|
||||
|
||||
* redirect_uri - A redirection URI where the response will be sent.
|
||||
|
||||
There is one method to construct and send the request to the Account Chooser:
|
||||
|
||||
a. Simple Request Method
|
||||
|
||||
#### Simple Request Method
|
||||
|
||||
The Client prepares an Account Chooser Request to the Account Chooser using the appropriate parameters. If using the HTTP "GET" method, the request parameters are serialized using URI query string serialization. If using the HTTP "POST" method, the request parameters are serialized using form serialization.
|
||||
|
||||
The following is a non-normative example of an Account Chooser Request URL. Line wraps are for display purposes only.
|
||||
|
||||
http://server.example.com/chooser?
|
||||
redirect_uri=https%3A%2F%2Fclient.example.com%2Fopenid_connect_login
|
||||
|
||||
#### Client sends a request to the Account Chooser
|
||||
|
||||
Having constructed the Account Chooser Request, the Client sends it to the Account Chooser. This MAY happen via redirect, hyperlinking, or any other means of directing the User-Agent to the Account Chooser URL.
|
||||
|
||||
Following is a non-normative example using HTTP redirect. Line wraps are for display purposes only.
|
||||
|
||||
HTTP/1.1 302 Found
|
||||
Location: https://server.example.com/chooser?
|
||||
redirect_uri=https%3A%2F%2Fclient.example.com%2Fopenid_connect_login
|
||||
|
||||
#### Account Chooser Sends the End-User back to the Client
|
||||
|
||||
After the End-User has select an OpenID Connect Server, it issues an Account Chooser Response and delivers it to the Client by adding the response parameters to redirect_uri specified in the Account Choose Request using the "application/x-www-form-urlencoded" format.
|
||||
|
||||
The following response parameters are included:
|
||||
|
||||
* oidc_alias - REQUIRED. The key used to configure the Client for its request of the selected OIDC server.
|
||||
|
||||
The following is non-normative example of a responses. Line wraps are for display purposes only.
|
||||
|
||||
HTTP/1.1 302 Found
|
||||
Location: https://client.example.com/openid_connect_login?
|
||||
oidc_alias=OIDC%20Server%201
|
||||
|
||||
#### End-User refuses to select an OIDC Server
|
||||
|
||||
If the End-User refuses to select an OIDC server, the Account Chooser MUST return an error response. The Account Chooser returns the Client via the redirection URI specified in the Account Chooser Request with the appropriate error parameters. No other parameters SHOULD be returned.
|
||||
|
||||
The error response parameters are the following:
|
||||
|
||||
* error - REQUIRED. The error code.
|
||||
* error_description - OPTIONAL. A human-readable UTF-8 encoded text description of the error.
|
||||
|
||||
The response parameters are added to the query component of the redirection URI.
|
||||
|
||||
The following is a non-normative example. Line wraps after the second line are for the display purposes only.
|
||||
|
||||
HTTP/1.1 302 Found
|
||||
Location: https://client.example.com/openid_connect_login?
|
||||
error=end_user_cancelled
|
||||
&error_description=The%20end%20user%20refused%20to%20select%20an%20OIDC%20server
|
||||
|
||||
### Modification to existing Client
|
||||
|
||||
#### Modifications to Client Configuration
|
||||
|
||||
The configuration of the filter would change by adding a OIDCServers property to the Client containing a map of OIDC servers, and a AccountChooserURI to denote the URI of the Account Chooser 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.OpenIdConnectAuthenticationFilter">
|
||||
class="org.mitre.openid.connect.client.OIDCAuthenticationUsingChooserFilter">
|
||||
<property name="errorRedirectURI" value="/login.jsp?authfail=openid" />
|
||||
<property name="authenticationManager" ref="authenticationManager" />
|
||||
<property name="AccountChooserURI"
|
||||
<property name="accountChooserURI"
|
||||
value="http://sever.example.com:8080/account-chooser" />
|
||||
<property name="accountChooserClientID" value="FGWEUIASJK" />
|
||||
<property name="oidcServerConfigs">
|
||||
<map>
|
||||
<entry key="OIDC Server 1">
|
||||
<entry key="http://sever.example.com:8080/Fopenid-connect-server">
|
||||
<bean class="org.mitre.openid.connect.client.OIDCServerConfiguration">
|
||||
<property name="authorizationEndpointURI"
|
||||
value="http://sever.example.com:8080/openid-connect-server/oauth/authorize" />
|
||||
|
@ -180,17 +91,103 @@ The configuration of the filter would change by adding a OIDCServers property to
|
|||
<property name="clientSecret" value="someClientSecret" />
|
||||
</bean>
|
||||
</entry>
|
||||
<entry key="OIDC Server 2">
|
||||
...
|
||||
<entry key=". . .
|
||||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
In cases where the Account Chooser will not be used, the Client will be configured with authorizationEndpointURI, tokenEndpointURI, checkIDEndpointURI, clientId, and clientSecret as the Client is presently.
|
||||
|
||||
[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"
|
||||
[Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client"
|
||||
[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"
|
||||
|
|
|
@ -26,7 +26,9 @@ import java.security.PrivateKey;
|
|||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Signature;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -35,6 +37,7 @@ import java.util.Map;
|
|||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
@ -67,16 +70,73 @@ import com.google.gson.JsonParser;
|
|||
* @author nemonik
|
||||
*
|
||||
*/
|
||||
public class OpenIdConnectAuthenticationFilter extends
|
||||
public class AbstractOIDCAuthenticationFilter extends
|
||||
AbstractAuthenticationProcessingFilter {
|
||||
|
||||
private final static int HTTP_SOCKET_TIMEOUT = 30000;
|
||||
private final static String SCOPE = "openid";
|
||||
private final static int KEY_SIZE = 1024;
|
||||
private final static String SIGNING_ALGORITHM = "SHA256withRSA";
|
||||
private final static String NONCE_SIGNATURE_COOKIE_NAME = "nonce";
|
||||
private final static String OIDC_ALIAS_COOKIE_NAME = "oidc_alias";
|
||||
private final static String FILTER_PROCESSES_URL = "/openid_connect_login";
|
||||
/**
|
||||
* Used to remove parameters from a Request before passing it down the chain...
|
||||
*
|
||||
* @author nemonik
|
||||
*
|
||||
*/
|
||||
class SanatizedRequest extends HttpServletRequestWrapper {
|
||||
|
||||
private List<String> paramsToBeSanatized;
|
||||
|
||||
public SanatizedRequest(HttpServletRequest request,
|
||||
String[] paramsToBeSanatized) {
|
||||
super(request);
|
||||
|
||||
this.paramsToBeSanatized = Arrays.asList(paramsToBeSanatized);
|
||||
}
|
||||
|
||||
public String getParameter(String name) {
|
||||
if (paramsToBeSanatized.contains(name)) {
|
||||
return null;
|
||||
} else {
|
||||
return super.getParameter(name);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, String[]> getParameterMap() {
|
||||
Map<String, String[]> params = super.getParameterMap();
|
||||
|
||||
for (String paramToBeSanatized : paramsToBeSanatized) {
|
||||
params.remove(paramToBeSanatized);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
public Enumeration<String> getParameterNames() {
|
||||
|
||||
ArrayList<String> paramNames = Collections.list(super
|
||||
.getParameterNames());
|
||||
|
||||
for (String paramToBeSanatized : paramsToBeSanatized) {
|
||||
paramNames.remove(paramToBeSanatized);
|
||||
}
|
||||
|
||||
return Collections.enumeration(paramNames);
|
||||
}
|
||||
|
||||
public String[] getParameterValues(String name) {
|
||||
|
||||
if (paramsToBeSanatized.contains(name)) {
|
||||
return null;
|
||||
} else {
|
||||
return super.getParameterValues(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final static int HTTP_SOCKET_TIMEOUT = 30000;
|
||||
protected final static String SCOPE = "openid";
|
||||
protected final static int KEY_SIZE = 1024;
|
||||
protected final static String SIGNING_ALGORITHM = "SHA256withRSA";
|
||||
protected final static String NONCE_SIGNATURE_COOKIE_NAME = "nonce";
|
||||
|
||||
protected final static String FILTER_PROCESSES_URL = "/openid_connect_login";
|
||||
|
||||
/**
|
||||
* Builds the redirect_uri that will be sent to the Authorization Endpoint.
|
||||
|
@ -105,7 +165,9 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
String name = (String) e.nextElement();
|
||||
|
||||
if ((ignore == null) || (!ignore.contains(name))) {
|
||||
|
||||
// Assume for simplicity that there is only one value
|
||||
|
||||
String value = request.getParameter(name);
|
||||
|
||||
if (value == null) {
|
||||
|
@ -150,13 +212,19 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
char appendChar = '?';
|
||||
|
||||
for (Map.Entry<String, String> param : queryStringFields.entrySet()) {
|
||||
|
||||
try {
|
||||
|
||||
URLBuilder.append(appendChar).append(param.getKey())
|
||||
.append('=')
|
||||
.append(URLEncoder.encode(param.getValue(), "UTF-8"));
|
||||
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
|
||||
throw new IllegalStateException(uee);
|
||||
|
||||
}
|
||||
|
||||
appendChar = '&';
|
||||
}
|
||||
|
||||
|
@ -188,8 +256,11 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
.replace("=", "");
|
||||
|
||||
} catch (GeneralSecurityException generalSecurityException) {
|
||||
|
||||
// generalSecurityException.printStackTrace();
|
||||
|
||||
throw new IllegalStateException(generalSecurityException);
|
||||
|
||||
}
|
||||
|
||||
return signature;
|
||||
|
@ -216,39 +287,37 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
return signer.verify(sigBytes);
|
||||
|
||||
} catch (GeneralSecurityException generalSecurityException) {
|
||||
|
||||
// generalSecurityException.printStackTrace();
|
||||
|
||||
throw new IllegalStateException(generalSecurityException);
|
||||
|
||||
} catch (UnsupportedEncodingException unsupportedEncodingException) {
|
||||
|
||||
// unsupportedEncodingException.printStackTrace();
|
||||
|
||||
throw new IllegalStateException(unsupportedEncodingException);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private String errorRedirectURI;
|
||||
protected String errorRedirectURI;
|
||||
|
||||
private OIDCServerConfiguration oidcServerConfig;
|
||||
protected String scope;
|
||||
|
||||
private String accountChooserURI;
|
||||
protected int httpSocketTimeout = HTTP_SOCKET_TIMEOUT;
|
||||
|
||||
private Map<String, ? extends OIDCServerConfiguration> oidcServerConfigs = new HashMap<String, OIDCServerConfiguration>();
|
||||
protected PublicKey publicKey;
|
||||
|
||||
private String scope;
|
||||
protected PrivateKey privateKey;
|
||||
|
||||
private int httpSocketTimeout = HTTP_SOCKET_TIMEOUT;
|
||||
|
||||
private PublicKey publicKey;
|
||||
|
||||
private PrivateKey privateKey;
|
||||
|
||||
private Signature signer;
|
||||
protected Signature signer;
|
||||
|
||||
/**
|
||||
* OpenIdConnectAuthenticationFilter constructor
|
||||
*/
|
||||
protected OpenIdConnectAuthenticationFilter() {
|
||||
protected AbstractOIDCAuthenticationFilter() {
|
||||
super(FILTER_PROCESSES_URL);
|
||||
|
||||
oidcServerConfig = new OIDCServerConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -258,40 +327,6 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
Assert.notNull(errorRedirectURI,
|
||||
"An Error Redirect URI must be supplied");
|
||||
|
||||
try {
|
||||
|
||||
// Validating configuration w/ Account Chooser UI Application
|
||||
// settings
|
||||
|
||||
Assert.notNull(
|
||||
oidcServerConfigs,
|
||||
"Server Configurations must be supplied if the Account Chooser UI Application is to be used.");
|
||||
|
||||
Assert.notNull(
|
||||
accountChooserURI,
|
||||
"Account Chooser URI must be supplied if the Account Chooser UI Application is to be used.");
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
// Failing over to validating configuration w/o Account Chooser UI
|
||||
// Application settings
|
||||
|
||||
Assert.notNull(oidcServerConfig.getAuthorizationEndpointURI(),
|
||||
"An Authorization Endpoint URI must be supplied");
|
||||
|
||||
Assert.notNull(oidcServerConfig.getTokenEndpointURI(),
|
||||
"A Token ID Endpoint URI must be supplied");
|
||||
|
||||
Assert.notNull(oidcServerConfig.getCheckIDEndpointURI(),
|
||||
"A Check ID Endpoint URI must be supplied");
|
||||
|
||||
Assert.notNull(oidcServerConfig.getClientId(),
|
||||
"A Client ID must be supplied");
|
||||
|
||||
Assert.notNull(oidcServerConfig.getClientSecret(),
|
||||
"A Client Secret must be supplied");
|
||||
}
|
||||
|
||||
KeyPairGenerator keyPairGenerator;
|
||||
|
||||
try {
|
||||
|
@ -325,96 +360,29 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
HttpServletResponse response) throws AuthenticationException,
|
||||
IOException, ServletException {
|
||||
|
||||
// Enter AuthenticationFilter here...
|
||||
|
||||
if (StringUtils.isNotBlank(request.getParameter("error"))) {
|
||||
|
||||
handleError(request, response);
|
||||
|
||||
} else if (request.getParameter("code") != null) {
|
||||
|
||||
return handleAuthorizationGrantResponse(request);
|
||||
|
||||
} else if (StringUtils.isNotBlank(accountChooserURI)) {
|
||||
|
||||
String oidcAlias = request.getParameter("oidc_alias");
|
||||
|
||||
if (StringUtils.isNotBlank(oidcAlias)) {
|
||||
|
||||
// Account Chooser UI selects the appropriate OIDC Server configuration
|
||||
|
||||
OIDCServerConfiguration oidcServerConfig = oidcServerConfigs
|
||||
.get(oidcAlias);
|
||||
|
||||
if (oidcServerConfig != null) {
|
||||
|
||||
Cookie oidcAliasCookie = new Cookie(OIDC_ALIAS_COOKIE_NAME, oidcAlias);
|
||||
response.addCookie(oidcAliasCookie);
|
||||
|
||||
handleAuthorizationRequest(request, response, oidcServerConfig);
|
||||
|
||||
} else {
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Security Filter not configured for OIDC Alias: "
|
||||
+ oidcAlias);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// redirect the End-User to the configured Account Chooser UI
|
||||
|
||||
Map<String, String> urlVariables = new HashMap<String, String>();
|
||||
|
||||
urlVariables.put("redirect_uri", OpenIdConnectAuthenticationFilter
|
||||
.buildRedirectURI(request, null));
|
||||
|
||||
response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL(
|
||||
accountChooserURI, urlVariables));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Use configuration that doesn't involve the Account Chooser UI.
|
||||
|
||||
handleAuthorizationRequest(request, response, this.oidcServerConfig);
|
||||
}
|
||||
logger.debug("Request: " + request.getRequestURI() + (StringUtils.isNotBlank(request.getQueryString()) ? "?"
|
||||
+ request.getQueryString() : "") );
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getAccountChooserURI() {
|
||||
return accountChooserURI;
|
||||
}
|
||||
|
||||
public Map<String, ? extends OIDCServerConfiguration> getOidcServerConfigs() {
|
||||
return oidcServerConfigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the authorization grant response
|
||||
*
|
||||
* @param authorizationGrant
|
||||
* The Authorization grant code
|
||||
* @param request
|
||||
* The request from which to extract parameters and perform the
|
||||
* authentication
|
||||
* @return The authenticated user token, or null if authentication is
|
||||
* incomplete.
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
private Authentication handleAuthorizationGrantResponse(
|
||||
HttpServletRequest request) {
|
||||
protected Authentication handleAuthorizationGrantResponse(
|
||||
String authorizationGrant, HttpServletRequest request,
|
||||
OIDCServerConfiguration serverConfig) {
|
||||
|
||||
final boolean debug = logger.isDebugEnabled();
|
||||
|
||||
OIDCServerConfiguration serverConfig = this.oidcServerConfig;
|
||||
|
||||
// Which OIDC configuration?
|
||||
Cookie oidcAliasCookie = WebUtils.getCookie(request, OIDC_ALIAS_COOKIE_NAME);
|
||||
|
||||
if (oidcAliasCookie != null) {
|
||||
serverConfig = oidcServerConfigs.get(oidcAliasCookie.getValue());
|
||||
}
|
||||
|
||||
String authorizationGrant = request.getParameter("code");
|
||||
|
||||
// Handle Token Endpoint interaction
|
||||
HttpClient httpClient = new DefaultHttpClient();
|
||||
|
@ -426,11 +394,11 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
// TODO: basic auth is untested (it wasn't working last I
|
||||
// tested)
|
||||
// UsernamePasswordCredentials credentials = new
|
||||
// UsernamePasswordCredentials(
|
||||
// clientId, clientSecret);
|
||||
// ((DefaultHttpClient) httpClient).getCredentialsProvider()
|
||||
// .setCredentials(AuthScope.ANY, credentials);
|
||||
//
|
||||
// UsernamePasswordCredentials(serverConfig.getClientId(),
|
||||
// serverConfig.getClientSecret());
|
||||
// ((DefaultHttpClient)
|
||||
// httpClient).getCredentialsProvider().setCredentials(AuthScope.ANY,
|
||||
// credentials);
|
||||
|
||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
|
||||
httpClient);
|
||||
|
@ -440,23 +408,24 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
|
||||
form.add("grant_type", "authorization_code");
|
||||
form.add("code", authorizationGrant);
|
||||
form.add("redirect_uri", OpenIdConnectAuthenticationFilter
|
||||
.buildRedirectURI(request, new String[] { "code" }));
|
||||
form.add("redirect_uri", AbstractOIDCAuthenticationFilter
|
||||
.buildRedirectURI(request, null));
|
||||
|
||||
// pass clientId and clientSecret in post of request
|
||||
form.add("client_id", serverConfig.getClientId());
|
||||
form.add("client_secret", serverConfig.getClientSecret());
|
||||
|
||||
if (debug) {
|
||||
logger.debug("tokenEndpointURI = " + serverConfig.getTokenEndpointURI());
|
||||
logger.debug("tokenEndpointURI = "
|
||||
+ serverConfig.getTokenEndpointURI());
|
||||
logger.debug("form = " + form);
|
||||
}
|
||||
|
||||
String jsonString = null;
|
||||
|
||||
try {
|
||||
jsonString = restTemplate.postForObject(serverConfig.getTokenEndpointURI(), form,
|
||||
String.class);
|
||||
jsonString = restTemplate.postForObject(
|
||||
serverConfig.getTokenEndpointURI(), form, String.class);
|
||||
} catch (HttpClientErrorException httpClientErrorException) {
|
||||
|
||||
// Handle error
|
||||
|
@ -554,8 +523,9 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
jsonString = null;
|
||||
|
||||
try {
|
||||
jsonString = restTemplate.postForObject(serverConfig.getCheckIDEndpointURI(),
|
||||
form, String.class);
|
||||
jsonString = restTemplate.postForObject(
|
||||
serverConfig.getCheckIDEndpointURI(), form,
|
||||
String.class);
|
||||
} catch (HttpClientErrorException httpClientErrorException) {
|
||||
|
||||
// Handle error
|
||||
|
@ -651,7 +621,7 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
* @throws IOException
|
||||
* If an input or output exception occurs
|
||||
*/
|
||||
private void handleAuthorizationRequest(HttpServletRequest request,
|
||||
protected void handleAuthorizationRequest(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
OIDCServerConfiguration serverConfiguration) throws IOException {
|
||||
|
||||
|
@ -662,7 +632,7 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
urlVariables.put("response_type", "code");
|
||||
urlVariables.put("client_id", serverConfiguration.getClientId());
|
||||
urlVariables.put("scope", scope);
|
||||
urlVariables.put("redirect_uri", OpenIdConnectAuthenticationFilter
|
||||
urlVariables.put("redirect_uri", AbstractOIDCAuthenticationFilter
|
||||
.buildRedirectURI(request, null));
|
||||
|
||||
// Create a string value used to associate a user agent session
|
||||
|
@ -684,8 +654,13 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
|
||||
// TODO: display, prompt, request, request_uri
|
||||
|
||||
response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL(
|
||||
serverConfiguration.getAuthorizationEndpointURI(), urlVariables));
|
||||
String authRequest = AbstractOIDCAuthenticationFilter
|
||||
.buildURL(serverConfiguration.getAuthorizationEndpointURI(),
|
||||
urlVariables);
|
||||
|
||||
logger.debug("Auth Request: " + authRequest);
|
||||
|
||||
response.sendRedirect(authRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -699,7 +674,7 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
* @throws IOException
|
||||
* If an input or output exception occurs
|
||||
*/
|
||||
private void handleError(HttpServletRequest request,
|
||||
protected void handleError(HttpServletRequest request,
|
||||
HttpServletResponse response) throws IOException {
|
||||
|
||||
String error = request.getParameter("error");
|
||||
|
@ -718,44 +693,15 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
requestParams.put("error_uri", errorURI);
|
||||
}
|
||||
|
||||
response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL(
|
||||
response.sendRedirect(AbstractOIDCAuthenticationFilter.buildURL(
|
||||
errorRedirectURI, requestParams));
|
||||
}
|
||||
|
||||
public void setAccountChooserURI(String accountChooserURI) {
|
||||
this.accountChooserURI = accountChooserURI;
|
||||
}
|
||||
|
||||
public void setAuthorizationEndpointURI(String authorizationEndpointURI) {
|
||||
oidcServerConfig.setAuthorizationEndpointURI(authorizationEndpointURI);
|
||||
}
|
||||
|
||||
public void setCheckIDEndpointURI(String checkIDEndpointURI) {
|
||||
oidcServerConfig.setCheckIDEndpointURI(checkIDEndpointURI);
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
oidcServerConfig.setClientId(clientId);
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret) {
|
||||
oidcServerConfig.setClientSecret(clientSecret);
|
||||
}
|
||||
|
||||
public void setErrorRedirectURI(String errorRedirectURI) {
|
||||
this.errorRedirectURI = errorRedirectURI;
|
||||
}
|
||||
|
||||
public void setOidcServerConfigs(
|
||||
Map<String, ? extends OIDCServerConfiguration> oidcServerConfigs) {
|
||||
this.oidcServerConfigs = oidcServerConfigs;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public void setTokenEndpointURI(String tokenEndpointURI) {
|
||||
oidcServerConfig.setTokenEndpointURI(tokenEndpointURI);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
/*******************************************************************************
|
||||
* 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.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The OpenID Connect Authentication Filter
|
||||
*
|
||||
* See README.md to to configure
|
||||
*
|
||||
* @author nemonik
|
||||
*
|
||||
*/
|
||||
public class OIDCAuthenticationFilter extends AbstractOIDCAuthenticationFilter {
|
||||
|
||||
protected OIDCServerConfiguration oidcServerConfig;
|
||||
|
||||
/**
|
||||
* OpenIdConnectAuthenticationFilter constructor
|
||||
*/
|
||||
protected OIDCAuthenticationFilter() {
|
||||
super();
|
||||
|
||||
oidcServerConfig = new OIDCServerConfiguration();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.mitre.openid.connect.client.AbstractOIDCAuthenticationFilter#
|
||||
* afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
|
||||
// Validating configuration
|
||||
|
||||
Assert.notNull(oidcServerConfig.getAuthorizationEndpointURI(),
|
||||
"An Authorization Endpoint URI must be supplied");
|
||||
|
||||
Assert.notNull(oidcServerConfig.getTokenEndpointURI(),
|
||||
"A Token ID Endpoint URI must be supplied");
|
||||
|
||||
Assert.notNull(oidcServerConfig.getCheckIDEndpointURI(),
|
||||
"A Check ID Endpoint URI must be supplied");
|
||||
|
||||
Assert.notNull(oidcServerConfig.getClientId(),
|
||||
"A Client ID must be supplied");
|
||||
|
||||
Assert.notNull(oidcServerConfig.getClientSecret(),
|
||||
"A Client Secret must be supplied");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.mitre.openid.connect.client.AbstractOIDCAuthenticationFilter#
|
||||
* attemptAuthentication(javax.servlet.http.HttpServletRequest,
|
||||
* javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request,
|
||||
HttpServletResponse response) throws AuthenticationException,
|
||||
IOException, ServletException {
|
||||
|
||||
// Enter AuthenticationFilter here...
|
||||
|
||||
super.attemptAuthentication(request, response);
|
||||
|
||||
if (StringUtils.isNotBlank(request.getParameter("error"))) {
|
||||
|
||||
handleError(request, response);
|
||||
|
||||
} else if (StringUtils.isNotBlank(request.getParameter("code"))) {
|
||||
|
||||
return handleAuthorizationGrantResponse(
|
||||
request.getParameter("code"), new SanatizedRequest(request,
|
||||
new String[] { "code" }), oidcServerConfig);
|
||||
|
||||
} else {
|
||||
|
||||
handleAuthorizationRequest(request, response, oidcServerConfig);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setAuthorizationEndpointURI(String authorizationEndpointURI) {
|
||||
oidcServerConfig.setAuthorizationEndpointURI(authorizationEndpointURI);
|
||||
}
|
||||
|
||||
public void setCheckIDEndpointURI(String checkIDEndpointURI) {
|
||||
oidcServerConfig.setCheckIDEndpointURI(checkIDEndpointURI);
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
oidcServerConfig.setClientId(clientId);
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret) {
|
||||
oidcServerConfig.setClientSecret(clientSecret);
|
||||
}
|
||||
|
||||
public void setErrorRedirectURI(String errorRedirectURI) {
|
||||
this.errorRedirectURI = errorRedirectURI;
|
||||
}
|
||||
|
||||
public void setTokenEndpointURI(String tokenEndpointURI) {
|
||||
oidcServerConfig.setTokenEndpointURI(tokenEndpointURI);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*******************************************************************************
|
||||
* 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.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* The OpenID Connect Authentication Filter using Acount Chooser UI Application
|
||||
*
|
||||
* See README.md to to configure
|
||||
*
|
||||
* @author nemonik
|
||||
*
|
||||
*/
|
||||
public class OIDCAuthenticationUsingChooserFilter extends
|
||||
AbstractOIDCAuthenticationFilter {
|
||||
|
||||
protected final static String ISSUER_COOKIE_NAME = "issuer";
|
||||
|
||||
protected String accountChooserURI;
|
||||
|
||||
protected String accountChooserClientID;
|
||||
|
||||
protected Map<String, ? extends OIDCServerConfiguration> oidcServerConfigs = new HashMap<String, OIDCServerConfiguration>();
|
||||
|
||||
/**
|
||||
* OpenIdConnectAuthenticationFilter constructor
|
||||
*/
|
||||
protected OIDCAuthenticationUsingChooserFilter() {
|
||||
super();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.mitre.openid.connect.client.AbstractOIDCAuthenticationFilter#
|
||||
* afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
|
||||
// Validating configuration
|
||||
|
||||
Assert.notNull(
|
||||
oidcServerConfigs,
|
||||
"Server Configurations must be supplied if the Account Chooser UI Application is to be used.");
|
||||
|
||||
Assert.notNull(
|
||||
accountChooserURI,
|
||||
"Account Chooser URI must be supplied if the Account Chooser UI Application is to be used.");
|
||||
|
||||
Assert.notNull(
|
||||
accountChooserClientID,
|
||||
"Account Chooser Client ID must be supplied if the Account Chooser UI Application is to be used.");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.security.web.authentication.
|
||||
* AbstractAuthenticationProcessingFilter
|
||||
* #attemptAuthentication(javax.servlet.http.HttpServletRequest,
|
||||
* javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request,
|
||||
HttpServletResponse response) throws IOException,
|
||||
AuthenticationException, ServletException {
|
||||
|
||||
// Enter AuthenticationFilter here...
|
||||
super.attemptAuthentication(request, response);
|
||||
|
||||
if (StringUtils.isNotBlank(request.getParameter("error"))) {
|
||||
|
||||
handleError(request, response);
|
||||
|
||||
} else if (request.getParameter("code") != null) {
|
||||
|
||||
// Which OIDC configuration?
|
||||
Cookie issuerCookie = WebUtils.getCookie(request,
|
||||
ISSUER_COOKIE_NAME);
|
||||
|
||||
return handleAuthorizationGrantResponse(
|
||||
request.getParameter("code"), new SanatizedRequest(request,
|
||||
new String[] { "code" }),
|
||||
oidcServerConfigs.get(issuerCookie.getValue()));
|
||||
|
||||
} else {
|
||||
|
||||
String issuer = request.getParameter("issuer");
|
||||
|
||||
if (StringUtils.isNotBlank(issuer)) {
|
||||
|
||||
// Account Chooser UI provided and Issuer Identifier
|
||||
|
||||
OIDCServerConfiguration oidcServerConfig = oidcServerConfigs
|
||||
.get(issuer);
|
||||
|
||||
if (oidcServerConfig != null) {
|
||||
|
||||
// The Client is configured to support this Issuer
|
||||
// Identifier
|
||||
|
||||
Cookie issuerCookie = new Cookie(ISSUER_COOKIE_NAME,
|
||||
issuer);
|
||||
response.addCookie(issuerCookie);
|
||||
|
||||
handleAuthorizationRequest(new SanatizedRequest(request,
|
||||
new String[] { "issuer" }), response,
|
||||
oidcServerConfig);
|
||||
|
||||
} else {
|
||||
|
||||
// The Client is NOT configured to support this Issuer
|
||||
// Identifier
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Security Filter not configured for issuer: "
|
||||
+ issuer);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Redirect the End-User to the configured Account Chooser UI
|
||||
// application
|
||||
|
||||
Map<String, String> urlVariables = new HashMap<String, String>();
|
||||
|
||||
urlVariables.put("redirect_uri",
|
||||
OIDCAuthenticationUsingChooserFilter.buildRedirectURI(
|
||||
request, null));
|
||||
|
||||
urlVariables.put("client_id", accountChooserClientID);
|
||||
|
||||
response.sendRedirect(OIDCAuthenticationUsingChooserFilter
|
||||
.buildURL(accountChooserURI, urlVariables));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setAccountChooserClientID(String accountChooserClientID) {
|
||||
this.accountChooserClientID = accountChooserClientID;
|
||||
}
|
||||
|
||||
public void setAccountChooserURI(String accountChooserURI) {
|
||||
this.accountChooserURI = accountChooserURI;
|
||||
}
|
||||
|
||||
public void setOidcServerConfigs(
|
||||
Map<String, ? extends OIDCServerConfiguration> oidcServerConfigs) {
|
||||
this.oidcServerConfigs = oidcServerConfigs;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -19,18 +19,10 @@
|
|||
package org.mitre.oauth2.model;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.Basic;
|
||||
import javax.persistence.CollectionTable;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.*;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
|
@ -55,23 +47,23 @@ public class ClientDetailsEntity implements ClientDetails {
|
|||
|
||||
public enum AuthType {
|
||||
client_secret_post, client_secret_basic, client_secret_jwt, private_key_jwt
|
||||
};
|
||||
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Set<String> scope;
|
||||
private Set<String> authorizedGrantTypes;
|
||||
private Set<GrantedAuthority> authorities = Collections.emptySet();
|
||||
private String clientName;
|
||||
private String clientDescription;
|
||||
private boolean allowRefresh = false; // do we allow refresh tokens for this client?
|
||||
private Integer accessTokenTimeout; // in seconds
|
||||
private Integer refreshTokenTimeout; // in seconds
|
||||
private String owner; // userid of who registered it
|
||||
private Set<String> registeredRedirectUri;
|
||||
private Set<String> resourceIds;
|
||||
}
|
||||
|
||||
//Additional properties added by OpenID Connect Dynamic Client Registration spec
|
||||
private String clientId = "";
|
||||
private String clientSecret = "";
|
||||
private Set<String> scope = new HashSet<String>();
|
||||
private Set<String> authorizedGrantTypes = new HashSet<String>();
|
||||
private Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
|
||||
private String clientName = "";
|
||||
private String clientDescription = "";
|
||||
private boolean allowRefresh = false; // do we allow refresh tokens for this client?
|
||||
private Integer accessTokenTimeout = 0; // in seconds
|
||||
private Integer refreshTokenTimeout = 0; // in seconds
|
||||
private String owner = ""; // userid of who registered it
|
||||
private Set<String> registeredRedirectUri = new HashSet<String>();
|
||||
private Set<String> resourceIds = new HashSet<String>();
|
||||
|
||||
//Additional properties added by OpenID Connect Dynamic Client Registration spec
|
||||
//http://openid.net/specs/openid-connect-registration-1_0.html
|
||||
|
||||
/**
|
||||
|
@ -122,7 +114,7 @@ public class ClientDetailsEntity implements ClientDetails {
|
|||
/**
|
||||
* @return the clientId
|
||||
*/
|
||||
@Id
|
||||
@Id @GeneratedValue
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
|
|
@ -28,10 +28,14 @@ public interface ClientDetailsEntityService extends ClientDetailsService {
|
|||
public ClientDetailsEntity loadClientByClientId(String clientId) throws OAuth2Exception;
|
||||
|
||||
public ClientDetailsEntity createClient(String clientId, String clientSecret, Set<String> scope, Set<String> grantTypes, String redirectUri, Set<GrantedAuthority> authorities, Set<String> resourceIds, String name, String description, boolean allowRefresh, Integer accessTokenTimeout, Integer refreshTokenTimeout, String owner);
|
||||
|
||||
|
||||
public ClientDetailsEntity createClient(ClientDetailsEntity client);
|
||||
|
||||
public void deleteClient(ClientDetailsEntity client);
|
||||
|
||||
public ClientDetailsEntity updateClient(ClientDetailsEntity oldClient, ClientDetailsEntity newClient);
|
||||
|
||||
public ClientDetailsEntity saveClient(ClientDetailsEntity client);
|
||||
|
||||
public Collection<ClientDetailsEntity> getAllClients();
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>openid</name>
|
||||
<comment>Reference implementation of OpenID Connect spec (http://openid.net/connect/). NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.</comment>
|
||||
<projects>
|
||||
<project>spring-security-oauth2</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>openid</name>
|
||||
<comment>Reference implementation of OpenID Connect spec (http://openid.net/connect/). NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.</comment>
|
||||
<projects>
|
||||
<project>spring-security-oauth2</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
|
|
@ -5,10 +5,7 @@
|
|||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
|
||||
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
|
||||
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
|
||||
<dependent-module archiveName="spring-security-oauth2-1.0.0.BUILD-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/spring-security-oauth2-MITRE/spring-security-oauth2-MITRE">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module archiveName="openid-connect-common-0.1-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/openid-connect-common-MITRE/openid-connect-common-MITRE">
|
||||
<dependent-module archiveName="openid-connect-common-0.1-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/openid-connect-common/openid-connect-common">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<property name="java-output-path" value="/openid/target/classes"/>
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.mitre.oauth2.service.impl;
|
|||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntityFactory;
|
||||
|
@ -108,6 +109,15 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
|
|||
return client;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientDetailsEntity createClient(ClientDetailsEntity client) {
|
||||
|
||||
clientRepository.saveClient(client);
|
||||
|
||||
return client;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a client and all its associated tokens
|
||||
|
@ -139,6 +149,29 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
|
|||
throw new IllegalArgumentException("Neither old client or new client can be null!");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param client object to be saved
|
||||
* @return ClientDetailsEntity the saved object
|
||||
*/
|
||||
@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 || client.getClientId().equals("") || this.loadClientByClientId(client.getClientId()) == null) {
|
||||
client.setClientId(UUID.randomUUID().toString());
|
||||
return this.createClient(client);
|
||||
} else {
|
||||
return clientRepository.updateClient(client.getClientId(), client);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all clients in the system
|
||||
*/
|
||||
|
|
|
@ -15,16 +15,22 @@
|
|||
******************************************************************************/
|
||||
package org.mitre.openid.connect.web;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import org.mitre.oauth2.exception.ClientNotFoundException;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Michael Jett <mjett@mitre.org>
|
||||
|
@ -32,6 +38,7 @@ import java.util.Collection;
|
|||
|
||||
@Controller
|
||||
@RequestMapping("/api/clients")
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
public class ClientAPI {
|
||||
|
||||
@Autowired
|
||||
|
@ -44,8 +51,7 @@ public class ClientAPI {
|
|||
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@RequestMapping("")
|
||||
@RequestMapping(method = RequestMethod.GET, headers="Accept=application/json")
|
||||
public ModelAndView apiGetAllClients(ModelAndView modelAndView) {
|
||||
|
||||
Collection<ClientDetailsEntity> clients = clientService.getAllClients();
|
||||
|
@ -55,94 +61,51 @@ public class ClientAPI {
|
|||
return modelAndView;
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
* @param modelAndView
|
||||
* @param clientId
|
||||
* @param clientSecret
|
||||
* @param scope
|
||||
* @param grantTypes
|
||||
* @param redirectUri
|
||||
* @param authorities
|
||||
* @param name
|
||||
* @param description
|
||||
* @param allowRefresh
|
||||
* @param accessTokenTimeout
|
||||
* @param refreshTokenTimeout
|
||||
* @param owner
|
||||
* @return
|
||||
*//*
|
||||
@RequestMapping(method = RequestMethod.POST, headers = "Accept=application/json")
|
||||
public String apiAddClient(@RequestBody String json, Model m, Principal principal) {
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@RequestMapping("/add")
|
||||
public ModelAndView apiAddClient(ModelAndView modelAndView,
|
||||
@RequestParam String clientId, @RequestParam String clientSecret,
|
||||
@RequestParam String scope, // space delimited
|
||||
@RequestParam String grantTypes, // space delimited
|
||||
@RequestParam(required = false) String redirectUri,
|
||||
@RequestParam String authorities, // space delimited
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam(required = false, defaultValue = "false") boolean allowRefresh,
|
||||
@RequestParam(required = false) Long accessTokenTimeout,
|
||||
@RequestParam(required = false) Long refreshTokenTimeout,
|
||||
@RequestParam(required = false) String owner
|
||||
) {
|
||||
return null;
|
||||
ClientDetailsEntity client = new Gson().fromJson(json, ClientDetailsEntity.class);
|
||||
// set owners as current logged in user
|
||||
client.setOwner(principal.getName());
|
||||
m.addAttribute("entity", clientService.saveClient(client));
|
||||
|
||||
return "jsonClientView";
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
*
|
||||
* @param modelAndView
|
||||
* @param clientId
|
||||
* @return
|
||||
*//*
|
||||
@RequestMapping(value="/{id}", method = RequestMethod.PUT, headers = "Accept=application/json")
|
||||
public String apiUpdateClient(@PathVariable("id") String id, @RequestBody String json, Model m, Principal principal) {
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@RequestMapping("/delete")
|
||||
public ModelAndView apiDeleteClient(ModelAndView modelAndView,
|
||||
@RequestParam String clientId) {
|
||||
return null;
|
||||
ClientDetailsEntity client = new Gson().fromJson(json, ClientDetailsEntity.class);
|
||||
client.setClientId(id);
|
||||
// set owners as current logged in user
|
||||
client.setOwner(principal.getName());
|
||||
|
||||
m.addAttribute("entity", clientService.saveClient(client));
|
||||
|
||||
return "jsonClientView";
|
||||
}
|
||||
|
||||
@RequestMapping(value="/{id}", method=RequestMethod.DELETE, headers="Accept=application/json")
|
||||
public String apiDeleteClient(@PathVariable("id") String id, ModelAndView modelAndView) {
|
||||
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(id);
|
||||
clientService.deleteClient(client);
|
||||
|
||||
return "jsonClientView";
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
@RequestMapping(value="/{id}", method=RequestMethod.GET, headers="Accept=application/json")
|
||||
@ResponseBody
|
||||
public Object apiShowClient(@PathVariable("id") Long id, ModelAndView modelAndView) {
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(id.toString());
|
||||
if (client == null) {
|
||||
return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
/* *//**
|
||||
*
|
||||
* @param modelAndView
|
||||
* @param clientId
|
||||
* @param clientSecret
|
||||
* @param scope
|
||||
* @param grantTypes
|
||||
* @param redirectUri
|
||||
* @param authorities
|
||||
* @param name
|
||||
* @param description
|
||||
* @param allowRefresh
|
||||
* @param accessTokenTimeout
|
||||
* @param refreshTokenTimeout
|
||||
* @param owner
|
||||
* @return
|
||||
*//*
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@RequestMapping("/update")
|
||||
public ModelAndView apiUpdateClient(ModelAndView modelAndView,
|
||||
@RequestParam String clientId, @RequestParam String clientSecret,
|
||||
@RequestParam String scope, // space delimited
|
||||
@RequestParam String grantTypes, // space delimited
|
||||
@RequestParam(required = false) String redirectUri,
|
||||
@RequestParam String authorities, // space delimited
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam(required = false, defaultValue = "false") boolean allowRefresh,
|
||||
@RequestParam(required = false) Long accessTokenTimeout,
|
||||
@RequestParam(required = false) Long refreshTokenTimeout,
|
||||
@RequestParam(required = false) String owner
|
||||
) {
|
||||
return null;
|
||||
}*/
|
||||
modelAndView.addObject("entity", client);
|
||||
modelAndView.setViewName("jsonClientView");
|
||||
|
||||
return modelAndView;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,18 +25,18 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
|
||||
@Controller
|
||||
@RequestMapping("/")
|
||||
@PreAuthorize("hasRole('ROLE_USER')")
|
||||
public class ManagerController {
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_USER')")
|
||||
|
||||
@RequestMapping({"", "/home", "/index"})
|
||||
public String showHomePage() {
|
||||
return "home";
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@RequestMapping("/admin/manage/clients")
|
||||
@RequestMapping("/admin/manage/")
|
||||
public String showClientManager() {
|
||||
return "admin/manage/clients";
|
||||
return "admin/manage";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<script type="text/javascript" src="resources/bootstrap2/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="resources/js/underscore-min.js"></script>
|
||||
<script type="text/javascript" src="resources/js/backbone-min.js"></script>
|
||||
<script type="text/javascript" src="resources/js/backbone.validations.js"></script>
|
||||
<script type="text/javascript" src="resources/js/app.js"></script>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<ul class="nav nav-list">
|
||||
<security:authorize ifAnyGranted="ROLE_ADMIN">
|
||||
<li class="nav-header">Administrative</li>
|
||||
<li><a href="admin/manage/clients">Manage Clients</a></li>
|
||||
<li><a href="admin/manage/#clients">Manage Clients</a></li>
|
||||
<li><a href="#">White Lists</a></li>
|
||||
<li><a href="#">Black Lists</a></li>
|
||||
</security:authorize>
|
||||
|
|
|
@ -2,24 +2,88 @@
|
|||
|
||||
var ClientModel = Backbone.Model.extend({
|
||||
|
||||
// We can pass it default values.
|
||||
defaults:{
|
||||
name:null,
|
||||
redirectURL:"http://myURL.domain",
|
||||
grantType:["my grant type 1", "my grant type 2"],
|
||||
scope:["scope 1", "scope 2"],
|
||||
authority:"my authority",
|
||||
description:"my description",
|
||||
refreshTokens:false
|
||||
idAttribute: "clientId",
|
||||
|
||||
initialize: function () {
|
||||
|
||||
// bind validation errors to dom elements
|
||||
// this will display form elements in red if they are not valid
|
||||
this.bind('error', function(model, errs) {
|
||||
_.map(errs, function (val, elID) {
|
||||
$('#' + elID).addClass('error');
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
urlRoot:"/resources/test/json/clients.js"
|
||||
validate:{
|
||||
clientName:{
|
||||
/* required:true,
|
||||
pattern:/^[\w ]+$/,
|
||||
minlength:3,*/
|
||||
maxlength:100
|
||||
},
|
||||
clientDescription:{
|
||||
/*required:true,
|
||||
pattern:/^[\w ]+$/,
|
||||
minlength:3,*/
|
||||
maxlength:200
|
||||
},
|
||||
accessTokenTimeout: {
|
||||
required: true,
|
||||
type:"number"
|
||||
},
|
||||
refreshTokenTimeout: {
|
||||
required: true,
|
||||
type:"number"
|
||||
},
|
||||
registeredRedirectUri: {
|
||||
custom: 'validateURI'
|
||||
}
|
||||
},
|
||||
|
||||
validateURI: function(attributeName, attributeValue) {
|
||||
|
||||
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);
|
||||
|
||||
|
||||
for (var i in attributeValue) {
|
||||
if (!attributeValue[i].match(regex)) {
|
||||
return "Invalid URI";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
// We can pass it default values.
|
||||
defaults:{
|
||||
clientName:"",
|
||||
clientSecret:"",
|
||||
registeredRedirectUri:[""],
|
||||
authorizedGrantTypes:[],
|
||||
scope:["openid"],
|
||||
authorities:[],
|
||||
clientDescription:"",
|
||||
clientId:null,
|
||||
allowRefresh:false,
|
||||
accessTokenTimeout: 0,
|
||||
refreshTokenTimeout: 0
|
||||
},
|
||||
|
||||
urlRoot:"api/clients"
|
||||
|
||||
});
|
||||
|
||||
var ClientCollection = Backbone.Collection.extend({
|
||||
|
||||
initialize: function() {
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
model:ClientModel,
|
||||
url:"/resources/test/json/clients.js"
|
||||
url:"api/clients"
|
||||
});
|
||||
|
||||
|
||||
|
@ -34,7 +98,6 @@
|
|||
}
|
||||
|
||||
this.model.bind('change', this.render, this);
|
||||
//this.model.on('change', this.render)
|
||||
},
|
||||
|
||||
render:function (eventName) {
|
||||
|
@ -48,11 +111,28 @@
|
|||
},
|
||||
|
||||
editClient:function () {
|
||||
alert('edit');
|
||||
app.navigate('client/' + this.model.id, {trigger: true});
|
||||
},
|
||||
|
||||
deleteClient:function () {
|
||||
alert('delete');
|
||||
|
||||
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
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.clientListView.delegateEvents();
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
close:function () {
|
||||
|
@ -75,7 +155,7 @@
|
|||
|
||||
newClient:function () {
|
||||
this.remove();
|
||||
document.location.hash = 'new_client';
|
||||
app.navigate('client/new', {trigger: true});
|
||||
},
|
||||
|
||||
render:function (eventName) {
|
||||
|
@ -84,7 +164,7 @@
|
|||
$(this.el).html($('#tmpl-client-table').html());
|
||||
|
||||
_.each(this.model.models, function (client) {
|
||||
$("#client-table").append(new ClientView({model:client}).render().el);
|
||||
$("#client-table",this.el).append(new ClientView({model:client}).render().el);
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
|
@ -102,15 +182,55 @@
|
|||
}
|
||||
},
|
||||
|
||||
render:function (eventName) {
|
||||
events:{
|
||||
"click .btn-primary":"saveClient"
|
||||
},
|
||||
|
||||
var action = "Edit";
|
||||
saveClient:function (event) {
|
||||
|
||||
if (!this.model) {
|
||||
action = "New";
|
||||
$('.control-group').removeClass('error');
|
||||
|
||||
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'),
|
||||
accessTokenTimeout: $('#accessTokenTimeout input').val(),
|
||||
refreshTokenTimeout: $('#refreshTokenTimeout input').val(),
|
||||
scope:$.map($('#scope textarea').val().replace(/,$/,'').replace(/\s/g,' ').split(","), $.trim)
|
||||
});
|
||||
|
||||
if (valid) {
|
||||
this.model.save(this.model, {
|
||||
success:function () {
|
||||
app.navigate('clients', {trigger:true});
|
||||
},
|
||||
error:function () {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (this.model.isNew()) {
|
||||
var self = this;
|
||||
app.clientList.create(this.model, {
|
||||
success:function () {
|
||||
app.navigate('clients', {trigger:true});
|
||||
},
|
||||
error:function () {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$(this.el).html(this.template({action: action}));
|
||||
return false;
|
||||
},
|
||||
|
||||
render:function (eventName) {
|
||||
|
||||
$(this.el).html(this.template(this.model.toJSON()));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
@ -119,30 +239,53 @@
|
|||
var AppRouter = Backbone.Router.extend({
|
||||
|
||||
routes:{
|
||||
"":"list",
|
||||
"new_client":"newClient"
|
||||
"clients":"list",
|
||||
"client/new":"newClient",
|
||||
"client/:id":"editClient"
|
||||
},
|
||||
|
||||
initialize:function () {
|
||||
|
||||
this.clientList = new ClientCollection();
|
||||
this.clientListView = new ClientListView({model:this.clientList});
|
||||
|
||||
this.startAfter([this.clientList]);
|
||||
|
||||
},
|
||||
|
||||
startAfter:function (collections) {
|
||||
// Start history when required collections are loaded
|
||||
var start = _.after(collections.length, _.once(function () {
|
||||
Backbone.history.start()
|
||||
}));
|
||||
_.each(collections, function (collection) {
|
||||
collection.bind('reset', start, Backbone.history)
|
||||
});
|
||||
},
|
||||
|
||||
list:function () {
|
||||
|
||||
this.clientList = new ClientCollection();
|
||||
this.clientListView = new ClientListView({model:this.clientList});
|
||||
this.clientList.fetch();
|
||||
|
||||
$('#content').html(this.clientListView.render().el);
|
||||
this.clientListView.delegateEvents();
|
||||
},
|
||||
|
||||
newClient:function() {
|
||||
this.clientFormView = new ClientFormView();
|
||||
this.clientFormView = new ClientFormView({model:new ClientModel()});
|
||||
$('#content').html(this.clientFormView.render().el);
|
||||
},
|
||||
|
||||
editClient:function(id) {
|
||||
var client = this.clientList.get(id);
|
||||
this.clientFormView = new ClientFormView({model:client});
|
||||
$('#content').html(this.clientFormView.render().el);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// holds the global app.
|
||||
// this gets init after the templates load
|
||||
var app = null;
|
||||
|
||||
// main
|
||||
$(function () {
|
||||
|
||||
|
@ -150,8 +293,7 @@
|
|||
$.get('resources/template/client.html', function (templates) {
|
||||
$('body').append(templates);
|
||||
|
||||
var app = new AppRouter();
|
||||
Backbone.history.start();
|
||||
app = new AppRouter();
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,286 @@
|
|||
// Copyright (C) 2011 Neal Stewart
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
|
||||
(function(Backbone) {
|
||||
// Premade Validators
|
||||
Backbone.Validations = {};
|
||||
|
||||
var validators = {
|
||||
"custom" : function(methodName, attributeName, model, valueToSet) {
|
||||
return model[methodName](attributeName, valueToSet);
|
||||
},
|
||||
|
||||
"required" : function(attributeName, model, valueToSet) {
|
||||
var currentValue = model.get(attributeName);
|
||||
var isNotAlreadySet = _.isUndefined(currentValue);
|
||||
var isNotBeingSet = _.isUndefined(valueToSet);
|
||||
if (_.isNull(valueToSet) || valueToSet === "" || (isNotBeingSet && isNotAlreadySet)) {
|
||||
return "required";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
"in" : function(whitelist, attributeName, model, valueToSet) {
|
||||
return _.include(whitelist, valueToSet) ? undefined : "in";
|
||||
},
|
||||
|
||||
"email" : function(type, attributeName, model, valueToSet) {
|
||||
var emailRegex = new RegExp("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?");
|
||||
|
||||
if (_.isString(valueToSet) && !valueToSet.match(emailRegex)) {
|
||||
return "email";
|
||||
}
|
||||
},
|
||||
|
||||
"url" : function(type, attributeName, model, valueToSet) {
|
||||
// taken from jQuery UI validation
|
||||
var urlRegex = /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;
|
||||
if (_.isString(valueToSet) && !valueToSet.match(urlRegex)) {
|
||||
return "url";
|
||||
}
|
||||
},
|
||||
|
||||
"number" : function(type, attributeName, model, valueToSet) {
|
||||
return isNaN(valueToSet) ? 'number' : undefined;
|
||||
},
|
||||
|
||||
"pattern" : function(pattern, attributeName, model, valueToSet) {
|
||||
if (_.isString(valueToSet)) {
|
||||
if (pattern.test(valueToSet)) {
|
||||
return false;
|
||||
} else {
|
||||
return "pattern";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"min" : function(minimumValue, attributeName, model, valueToSet) {
|
||||
if (valueToSet < minimumValue) {
|
||||
return "min";
|
||||
}
|
||||
},
|
||||
|
||||
"max" : function(maximumValue, attributeName, model, valueToSet) {
|
||||
if (valueToSet > maximumValue) {
|
||||
return "max";
|
||||
}
|
||||
},
|
||||
|
||||
"minlength" : function(minlength, attributeName, model, valueToSet) {
|
||||
if (_.isString(valueToSet)) {
|
||||
if (valueToSet.length < minlength) { return "minlength"; }
|
||||
}
|
||||
},
|
||||
|
||||
"maxlength" : function(maxlength, attributeName, model, valueToSet) {
|
||||
if (_.isString(valueToSet)) {
|
||||
if (valueToSet.length > maxlength) { return "maxlength"; }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var customValidators = {};
|
||||
var getCustomValidator = function(name) {
|
||||
var cv = customValidators[name];
|
||||
if (!cv) { throw "custom validator '"+name+"' could not be found."; }
|
||||
return cv;
|
||||
};
|
||||
|
||||
Backbone.Validations.addValidator = function(name, validator) {
|
||||
if (validators.hasOwnProperty(name) || customValidators.hasOwnProperty(name)) {
|
||||
throw "existing validator";
|
||||
}
|
||||
customValidators[name] = validator;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
The newValidate method overrides validate in Backbone.Model.
|
||||
It has the same interface as the validate function which you
|
||||
would provide.
|
||||
|
||||
The returned object looks like this:
|
||||
|
||||
{
|
||||
attributeName : ["required", "of", "errors"],
|
||||
otherAttributeName: ["and", "so", "on"]
|
||||
}
|
||||
|
||||
*/
|
||||
function newValidate(attributes) {
|
||||
var errorsForAttribute,
|
||||
errorHasOccured,
|
||||
errors = {};
|
||||
|
||||
for (var attrName in this._attributeValidators) {
|
||||
var valueToSet = attributes[attrName];
|
||||
var validateAttribute = this._attributeValidators[attrName];
|
||||
if (validateAttribute) {
|
||||
errorsForAttribute = validateAttribute(this, valueToSet);
|
||||
}
|
||||
if (errorsForAttribute) {
|
||||
errorHasOccured = true;
|
||||
errors[attrName] = errorsForAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
return errorHasOccured ? errors : false;
|
||||
}
|
||||
|
||||
function createMinValidator(attributeName, minimumValue) {
|
||||
return _.bind(validators.min, null, minimumValue);
|
||||
}
|
||||
|
||||
function createMaxValidator(attributeName, maximumValue) {
|
||||
return _.bind(validators.max, null, maximumValue);
|
||||
}
|
||||
|
||||
|
||||
/* createValidator takes in:
|
||||
- the model
|
||||
- the name of the attribute
|
||||
- the type of validation
|
||||
- the description of the validation
|
||||
|
||||
returns a function that takes in:
|
||||
- the value being set for the attribute
|
||||
|
||||
and either returns nothing (undefined),
|
||||
or the error name (string).
|
||||
*/
|
||||
function createValidator(attributeName, type, description) {
|
||||
var validator,
|
||||
validatorMethod,
|
||||
customValidator;
|
||||
|
||||
if (type === "type") {
|
||||
type = description;
|
||||
}
|
||||
validator = validators[type];
|
||||
|
||||
if (!validator) { validator = getCustomValidator(type); }
|
||||
|
||||
if (!validator) { throw "Improper validation type '"+type+"'" ; }
|
||||
|
||||
if (type !== "required") { // doesn't need the description
|
||||
validator = _.bind(validator, null, description, attributeName);
|
||||
} else {
|
||||
validator = _.bind(validator, null, attributeName);
|
||||
}
|
||||
|
||||
return validator;
|
||||
}
|
||||
|
||||
function createAttributeValidator(attributeName, attributeDescription) {
|
||||
var validatorsForAttribute = [],
|
||||
type,
|
||||
desc;
|
||||
|
||||
for (type in attributeDescription) {
|
||||
desc = attributeDescription[type];
|
||||
validatorsForAttribute.push(createValidator(attributeName, type, desc));
|
||||
}
|
||||
|
||||
return function(model, valueToSet, hasOverridenError, options) {
|
||||
var validator,
|
||||
result,
|
||||
errors = [];
|
||||
|
||||
for (var i = 0, length = validatorsForAttribute.length; i < length; i++) {
|
||||
validator = validatorsForAttribute[i];
|
||||
result = validator(model, valueToSet);
|
||||
if (result) {
|
||||
if (_.isArray(result)) {
|
||||
errors = errors.concat(result);
|
||||
} else {
|
||||
errors.push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
return errors;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createValidators(modelValidations) {
|
||||
var attributeValidations,
|
||||
attributeValidators = {};
|
||||
|
||||
for (var attrName in modelValidations) {
|
||||
attributeValidations = modelValidations[attrName];
|
||||
attributeValidators[attrName] = createAttributeValidator(attrName, attributeValidations);
|
||||
}
|
||||
|
||||
return attributeValidators;
|
||||
}
|
||||
|
||||
var oldPerformValidation = Backbone.Model.prototype._performValidation;
|
||||
function newPerformValidation(attrs, options) {
|
||||
if (options.silent || !this.validate) return true;
|
||||
var errors = this.validate(attrs);
|
||||
if (errors) {
|
||||
if (options.error) {
|
||||
options.error(this, errors, options);
|
||||
} else {
|
||||
this.trigger('error', this, errors, options);
|
||||
_.each(errors, function(error, name) {
|
||||
this.trigger('error:' + name, this, errors, options);
|
||||
}, this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// save the old backbone
|
||||
var oldModel = Backbone.Model;
|
||||
|
||||
// Constructor for our new Validations Model
|
||||
Backbone.Validations.Model = Backbone.Model.extend({
|
||||
constructor : function() {
|
||||
// if they pass an object, construct the new validations
|
||||
if (typeof this.validate === "object" && this.validate !== null) {
|
||||
if (!this.constructor.prototype._attributeValidators) {
|
||||
this.constructor.prototype._attributeValidators = createValidators(this.validate);
|
||||
this.constructor.prototype.validate = newValidate;
|
||||
this.constructor.prototype._validate = newPerformValidation;
|
||||
}
|
||||
}
|
||||
|
||||
oldModel.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
// Override Backbone.Model with our new Model
|
||||
Backbone.Model = Backbone.Validations.Model;
|
||||
|
||||
|
||||
// Requisite noConflict
|
||||
Backbone.Validations.Model.noConflict = function() {
|
||||
Backbone.Model = oldModel;
|
||||
};
|
||||
|
||||
}(Backbone));
|
|
@ -1,16 +1,22 @@
|
|||
<script type="text/html" id="tmpl-client">
|
||||
|
||||
<td>
|
||||
<%=name%>
|
||||
</td>
|
||||
<td>
|
||||
<%=redirectURL%>
|
||||
<%=clientName%>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<% for (var i in grantType) { %>
|
||||
<% for (var i in registeredRedirectUri) { %>
|
||||
<li>
|
||||
<%=grantType[i]%>
|
||||
<%=registeredRedirectUri[i]%>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<% for (var i in authorizedGrantTypes) { %>
|
||||
<li>
|
||||
<%=authorizedGrantTypes[i]%>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
|
@ -24,14 +30,12 @@
|
|||
<% } %>
|
||||
</ul>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<%=authority%>
|
||||
</td>
|
||||
<td>
|
||||
<%=description%>
|
||||
<%=clientDescription%>
|
||||
</td>
|
||||
<td><input type="checkbox"
|
||||
<%=(refreshTokens == 1 ? 'checked' : '')%>
|
||||
<%=(allowRefresh == 1 ? 'checked' : '')%>
|
||||
value="" id="" name="" disabled>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -52,10 +56,9 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Redirect URL</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>
|
||||
|
@ -74,7 +77,7 @@
|
|||
|
||||
<script type="text/html" id="tmpl-client-form">
|
||||
|
||||
<h1><%=action%> Client</h1>
|
||||
<h1><%=(clientId == null ? 'New' : 'Edit')%> Client</h1>
|
||||
|
||||
|
||||
<div class="">
|
||||
|
@ -86,16 +89,24 @@
|
|||
|
||||
<div class="row-fluid">
|
||||
<div class="span6">
|
||||
<span class="control-group" id="clientName">
|
||||
<label>Client name</label>
|
||||
<input type="text" class="" placeholder="Type something"> <span class="help-inline">Associated help text!</span>
|
||||
|
||||
<label>Refresh URL</label>
|
||||
<input type="text" class="" placeholder="http://"><span class="help-inline">Associated help text!</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 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" id="textarea" placeholder="Type a description"
|
||||
rows="3"></textarea> <span class="help-inline">Associated help text!</span>
|
||||
<textarea class="input-xlarge" placeholder="Type a description" maxlength="200"
|
||||
rows="3"><%=clientDescription%></textarea> <span class="help-inline"></span>
|
||||
</span>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -106,9 +117,9 @@
|
|||
token after it
|
||||
expires.</p>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"> Allow refresh tokens?
|
||||
<input type="checkbox" id="allowRefresh" <%=(allowRefresh == true ? 'checked' : '')%>> Allow refresh tokens?
|
||||
</label>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
<button class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
@ -117,93 +128,53 @@
|
|||
|
||||
<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>
|
||||
|
||||
<div class="span4 control-group">
|
||||
<div class="span4">
|
||||
<span class="control-group" id="accessTokenTimeout">
|
||||
<label class="control-label" for="access-token-timeout-seconds" style="">Access Token
|
||||
Timeout</label>
|
||||
|
||||
<div class="controls form-horizontal" style="">
|
||||
<div class="input-append">
|
||||
<input type="text" class="" id="access-token-timeout-seconds" size="16"><span
|
||||
<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>
|
||||
</div>
|
||||
|
||||
</span>
|
||||
<span class="control-group" id="refreshTokenTimeout">
|
||||
<label class="control-label" for="refresh-token-timeout-seconds" style="">Refresh Token
|
||||
Timeout</label>
|
||||
|
||||
<div class="controls form-horizontal" style="">
|
||||
<div class="input-append">
|
||||
<input type="text" class="" id="refresh-token-timeout-seconds" size="16"><span
|
||||
<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>
|
||||
</div>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div 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"></textarea>
|
||||
<span class="help-block">
|
||||
Please enter scopes separated by commas
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
|
Loading…
Reference in New Issue