/**
* Copyright (c) 2011-2014, OpenIoT
*
* This library is free software; you can redistribute it and/or
* modify it either under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* (the "LGPL"). If you do not alter this
* notice, a recipient may use your version of this file under the LGPL.
*
* You should have received a copy of the LGPL along with this library
* in the file COPYING-LGPL-2.1; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
* OF ANY KIND, either express or implied. See the LGPL for
* the specific language governing rights and limitations.
*
* Contact: OpenIoT mailto: info@openiot.eu
*/
package org.openiot.security.client.rest;
import io.buji.pac4j.ClientRealm;
import io.buji.pac4j.ClientToken;
import io.buji.pac4j.NoAuthenticationException;
import io.buji.pac4j.filter.FilterHelper;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.util.CollectionUtils;
import org.openiot.security.client.ACRealm;
import org.openiot.security.client.ClearCacheListener;
import org.openiot.security.client.SecurityConstants;
import org.pac4j.core.client.BaseClient;
import org.pac4j.core.credentials.Credentials;
import org.pac4j.core.exception.HttpCommunicationException;
import org.pac4j.core.profile.CommonProfile;
import org.pac4j.oauth.client.BaseOAuthClient;
import org.pac4j.oauth.profile.JsonHelper;
import org.pac4j.oauth.profile.casoauthwrapper.CasOAuthWrapperProfile;
import org.scribe.model.OAuthConstants;
import org.scribe.model.ProxyOAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Verb;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
/**
* @author Mehdi Riahi
*
*/
public class CasOAuthClientRealmRest extends ClientRealm implements ACRealm {
private static Logger log = LoggerFactory.getLogger(CasOAuthClientRealmRest.class);
private String permissionsURL;
private List<ClearCacheListener> clearCacheListeners = new ArrayList<>();
/**
* Authenticates a user and retrieves its user profile.
*
* @param authenticationToken
* the authentication token
*/
@SuppressWarnings("unchecked")
protected AuthenticationInfo internalGetAuthenticationInfo(final AuthenticationToken authenticationToken) {
final ClientToken clientToken = (ClientToken) authenticationToken;
log.debug("clientToken : {}", clientToken);
// token must be provided
if (clientToken == null) {
return null;
}
// credentials
final Credentials credentials = (Credentials) clientToken.getCredentials();
log.debug("credentials : {}", credentials);
// client
final BaseClient<Credentials, CommonProfile> client = (BaseClient<Credentials, CommonProfile>) getClients().findClient(clientToken.getClientName());
log.debug("client : {}", client);
// finish authentication process : get the user profile
final CommonProfile profile = client.getUserProfile(credentials);
log.debug("profile : {}", profile);
// no profile
if (profile == null) {
final String message = "No profile retrieved from authentication using client : " + client + " and credentials : " + credentials;
log.info(message);
// save that this kind of authentication has already been attempted and returns a null
// profile
FilterHelper.setValue(client, "true");
throw new NoAuthenticationException(message);
}
// refresh authentication token with user id
final String userId = profile.getTypedId();
clientToken.setUserId(userId);
// create simple authentication info
final List<? extends Object> principals = CollectionUtils.asList(userId, profile);
final PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
return new SimpleAuthenticationInfo(principalCollection, credentials);
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
log.debug("clients : {}", getClients());
final BaseOAuthClient<?> client = (BaseOAuthClient<?>) getClients().findClient("CasOAuthWrapperClientRest");
log.debug("client : {}", client);
final CasOAuthWrapperProfile profile = principals.oneByType(CasOAuthWrapperProfile.class);
log.debug("profile: {} ", profile);
final String accessToken = profile.getAccessToken();
final ArrayNode roleNames = (ArrayNode) profile.getAttribute("role_name");
log.debug("accessToken: {} , key : {} ", accessToken, client.getKey());
// create simple authorization info
final SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// add roles
for (JsonNode element : roleNames) {
String role = element.asText();
log.debug("adding role: {}", role);
simpleAuthorizationInfo.addRole(role);
}
// add default role
simpleAuthorizationInfo.addRoles(split(getDefaultRoles()));
// get permissions
final String body = sendRequestForPermissions(accessToken, client);
JsonNode json = JsonHelper.getFirstNode(body);
if (json != null) {
json = json.get("role_permissions");
if (json != null) {
final Iterator<JsonNode> nodes = json.iterator();
while (nodes.hasNext()) {
for (Iterator<Entry<String, JsonNode>> fields = nodes.next().fields(); fields.hasNext();) {
Iterator<JsonNode> permIter = fields.next().getValue().iterator();
while (permIter.hasNext()) {
json = permIter.next();
String permission = json.asText();
simpleAuthorizationInfo.addStringPermission(permission);
log.debug("next permission: {}", permission);
}
}
}
}
}
// add default permissions
simpleAuthorizationInfo.addStringPermissions(split(getDefaultPermissions()));
return simpleAuthorizationInfo;
}
protected String sendRequestForPermissions(final String accessToken, final BaseOAuthClient<?> client) {
log.debug("accessToken : {} / permissionsUrl : {}", accessToken, permissionsURL);
final long t0 = System.currentTimeMillis();
final ProxyOAuthRequest request = new ProxyOAuthRequest(Verb.GET, permissionsURL, client.getConnectTimeout(), client.getReadTimeout(),
client.getProxyHost(), client.getProxyPort());
request.addQuerystringParameter(OAuthConstants.CLIENT_ID, client.getKey());
request.addQuerystringParameter(OAuthConstants.ACCESS_TOKEN, accessToken);
request.addQuerystringParameter(SecurityConstants.TARGET_CLIENT_ID, client.getKey());
request.addQuerystringParameter(SecurityConstants.USER_ACCESS_TOKEN, accessToken);
final Response response = request.send();
final int code = response.getCode();
final String body = response.getBody();
final long t1 = System.currentTimeMillis();
log.debug("Request took : " + (t1 - t0) + " ms for : " + permissionsURL);
log.debug("response code : {} / response body : {}", code, body);
if (code != 200) {
log.error("Failed to get permissions, code : " + code + " / body : " + body);
throw new HttpCommunicationException(code, body);
}
return body;
}
public String getPermissionsURL() {
return permissionsURL;
}
public void setPermissionsURL(String permissionsURL) {
this.permissionsURL = permissionsURL;
}
public void addClearCacheListener(ClearCacheListener listener) {
clearCacheListeners.add(listener);
}
@Override
public void onLogout(PrincipalCollection principals) {
super.onLogout(principals);
final CasOAuthWrapperProfile profile = principals.oneByType(CasOAuthWrapperProfile.class);
String accessToken = profile.getAccessToken();
CasOAuthWrapperClientRest client = (CasOAuthWrapperClientRest) getClients().findClient(CasOAuthWrapperClientRest.class.getSimpleName());
client.removeToken(accessToken);
}
@Override
protected void doClearCache(PrincipalCollection principals) {
for (ClearCacheListener listener : clearCacheListeners)
listener.clearCache(principals);
}
}