Commit 4a5f9374 authored by Matija Obreza's avatar Matija Obreza
Browse files

OAuth client list, updated keygenerator to include timestamp

parent 3060d246
......@@ -18,11 +18,14 @@ package org.genesys2.server.service.impl;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
......@@ -33,10 +36,11 @@ import org.genesys2.server.persistence.domain.OAuthRefreshTokenPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.common.util.SerializationUtils;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -45,15 +49,16 @@ import org.springframework.transaction.annotation.Transactional;
* OAuth2JPATokenStoreImpl based on JdbcTokenStore
*
* Original authors of JdbcTokenStore:
*
* @author Ken Dombeck
* @author Luke Taylor
* @author Dave Syer
*
* JPA:
* JPA:
* @author Matija Obreza
*/
@Service("tokenStore")
@Transactional
@Transactional(readOnly = false)
public class OAuth2JPATokenStoreImpl implements TokenStore {
private static final Log LOG = LogFactory.getLog(OAuth2JPATokenStoreImpl.class);
......@@ -63,11 +68,45 @@ public class OAuth2JPATokenStoreImpl implements TokenStore {
@Autowired
private OAuthRefreshTokenPersistence refreshTokenPersistence;
private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();
private AuthenticationKeyGenerator authenticationKeyGenerator = // new
// DefaultAuthenticationKeyGenerator();
new AuthenticationKeyGenerator() {
public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {
this.authenticationKeyGenerator = authenticationKeyGenerator;
}
private static final String CLIENT_ID = "client_id";
private static final String SCOPE = "scope";
private static final String USERNAME = "username";
public String extractKey(OAuth2Authentication authentication) {
Map<String, String> values = new LinkedHashMap<String, String>();
AuthorizationRequest authorizationRequest = authentication.getAuthorizationRequest();
if (!authentication.isClientOnly()) {
values.put(USERNAME, authentication.getName());
}
values.put(CLIENT_ID, authorizationRequest.getClientId());
if (authorizationRequest.getScope() != null) {
values.put(SCOPE, OAuth2Utils.formatParameterList(authorizationRequest.getScope()));
}
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK).");
}
try {
byte[] bytes = digest.digest(values.toString().getBytes("UTF-8"));
ByteBuffer bb = ByteBuffer.allocate(bytes.length + 8);
bb.put(bytes);
// Add timestamp
bb.putLong(System.currentTimeMillis());
return String.format("%032x", new BigInteger(1, bb.array()));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK).");
}
}
};
@Override
public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
......@@ -97,6 +136,7 @@ public class OAuth2JPATokenStoreImpl implements TokenStore {
String key = authenticationKeyGenerator.extractKey(authentication);
try {
// FIXME Dies with two keys issued to same user in same client
OAuthAccessToken persisted = accessTokenPersistence.findByAuthenticationId(key);
accessToken = deserializeAccessToken(persisted.getToken());
} catch (NullPointerException e) {
......@@ -208,7 +248,8 @@ public class OAuth2JPATokenStoreImpl implements TokenStore {
}
private void removeAccessToken(String tokenId) {
accessTokenPersistence.delete(tokenId);
if (accessTokenPersistence.exists(tokenId))
accessTokenPersistence.delete(tokenId);
}
@Override
......
package org.genesys2.server.servlet.controller;
import java.util.List;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
public abstract class ProfileController<T, ID> extends BaseController {
private final String viewPrefix = getViewPrefix();
/**
* Get details record for T with given identifier
*
* @param identifier
* @return
*/
protected abstract T getDetails(ID identifier);
/**
* Return list of T entries to display on index page.
*
* Example: "/admin/oauth"
*
* @return
*/
protected abstract List<T> list();
/**
* Get prefix to JSP views
*
* @return
*/
protected abstract String getViewPrefix();
/**
* List controllers
*
* @return
*/
@RequestMapping(value = "")
public String index(ModelMap model) {
model.addAttribute("items", list());
return viewPrefix + "/index";
}
/**
* View client details
*
* @param model
* @param clientId
* @return
*/
@RequestMapping(value = "/{id}")
public final String details(ModelMap model, @PathVariable("id") ID identifier) {
T item = getDetails(identifier);
if (item == null) {
_logger.warn("No such item " + identifier);
return "redirect:/admin/oauth/" + identifier + "/edit";
}
model.addAttribute("item", item);
return viewPrefix + "/details";
}
/**
* Overridable method to add extra items to the profile
*
* @param item
*/
public void addDetails(ModelMap model, T item) {
// NOOP
}
/**
* Edit client details
*
* @param model
* @param clientId
* @return
*/
@RequestMapping(value = "/{id}/edit")
public String edit(ModelMap model, @PathVariable("id") ID identifier) {
T item = getDetails(identifier);
if (item == null) {
return viewPrefix + "/edit";
}
model.addAttribute("item", item);
return viewPrefix + "/edit";
}
/**
* Edit client details
*
* @param model
* @param clientId
* @return
*/
@RequestMapping(value = "/update", method = RequestMethod.POST)
public String update(ModelMap model, @RequestParam(required = false, defaultValue = "", value = "id") ID identifier) {
T item = getDetails(identifier);
if (item == null) {
return viewPrefix + "/edit";
}
model.addAttribute("client", item);
return "redirect:.";
}
}
/**
* Copyright 2013 Global Crop Diversity Trust
*
* 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.genesys2.server.servlet.controller.oauth;
import java.util.List;
import org.genesys2.server.service.OAuth2ClientDetailsService;
import org.genesys2.server.servlet.controller.ProfileController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Manage OAuth2 clients
*
* @author mobreza
*/
@Controller
@RequestMapping("/admin/oauth")
public class ClientController extends ProfileController<ClientDetails, String> {
@Autowired
private OAuth2ClientDetailsService clientDetailsService;
@Autowired
private TokenStore tokenStore;
@Override
protected ClientDetails getDetails(String identifier) {
return clientDetailsService.loadClientByClientId(identifier);
}
@Override
public void addDetails(ModelMap model, ClientDetails item) {
super.addDetails(model, item);
model.addAttribute("tokens", tokenStore.findTokensByClientId(item.getClientId()));
}
@Override
protected List<ClientDetails> list() {
return clientDetailsService.listClientDetails();
}
@Override
protected String getViewPrefix() {
return "/admin/oauth/client";
}
}
......@@ -253,4 +253,8 @@ oauth2.authorization-code=Authorization code
oauth2.authorization-code-instructions=Copy this authorization code:
oauth2.access-denied=Access denied
oauth2.access-denied-text=You have denied access to your resources.
\ No newline at end of file
oauth2.access-denied-text=You have denied access to your resources.
oauth-client.page.list.title=OAuth2 Clients
oauth-client.page.profile.title=OAuth2 Client: {0}
oauth-client.active-tokens=List of tokens issued
<!DOCTYPE html>
<%@include file="/WEB-INF/jsp/init.jsp"%>
<html>
<head>
<title><spring:message code="oauth-client.page.profile.title" arguments="${item.clientId}" argumentSeparator="|" /></title>
</head>
<body>
<h1>
<c:out value="${item.clientId}" />
</h1>
<div>
<c:forEach items="${item.authorities}" var="authority">
<c:out value="${authority}" />
</c:forEach>
</div>
<div>
<c:forEach items="${item.authorizedGrantTypes}" var="authGrantType">
<c:out value="${authGrantType}" />
</c:forEach>
</div>
<div>
<c:out value="${item.clientSecret}" />
</div>
<h2><spring:message code="oauth-client.active-tokens" /></h2>
<ul class="funny-list">
<c:forEach items="${tokens}" var="token">
<li><c:out value="${token}" /></li>
</c:forEach>
</ul>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<%@include file="/WEB-INF/jsp/init.jsp"%>
<html>
<head>
<title><spring:message code="oauth-client.page.profile.title" arguments="${item.clientId}" argumentSeparator="|" /></title>
<script type="text/javascript" src="/html/js/tinymce/tinymce.min.js"></script>
</head>
<body>
<h1>
<c:out value="${item.clientId}" />
</h1>
<form class="" action="<c:url value="/admin/oauth/update" />" method="post">
<div class="form-group">
<label for="blurp-body" class="control-label"><spring:message code="blurp.blurp-body" /></label>
<div class="controls">
<textarea id="blurp-body" name="blurp" class="span9 required html-editor">
<c:out value="${blurp.body}" />
</textarea>
</div>
</div>
<input type="submit" value="<spring:message code="blurp.update-blurp"/>" class="btn btn-primary" />
<a href="<c:url value="/geo/${country.code3.toLowerCase()}" />" class="btn btn-default"> <spring:message code="cancel" />
</a>
</form>
<script type="text/javascript">
jQuery(document).ready(function() {
tinyMCE.init({
selector : "#blurp-body.html-editor",
menubar : false,
statusbar : false,
height : 200,
plugins: "link autolink",
directionality: document.dir
});
});
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<%@include file="/WEB-INF/jsp/init.jsp"%>
<html>
<head>
<title><spring:message code="oauth-client.page.list.title" /></title>
</head>
<body>
<h1>
<spring:message code="oauth-client.page.list.title" />
</h1>
<ul class="funny-list">
<c:forEach items="${items}" var="item" varStatus="status">
<li class="${status.count % 2 == 0 ? 'even' : 'odd'}"><a href="<c:url value="/admin/oauth/${item.clientId}" />"><c:out value="${item.clientId}" /></a></li>
</c:forEach>
</ul>
</body>
</html>
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment