/*
* Copyright 2013, 2014, 2015 EnergyOS.org
*
* 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.energyos.espi.datacustodian.oauth;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.NoResultException;
import org.energyos.espi.common.domain.ApplicationInformation;
import org.energyos.espi.common.domain.Authorization;
import org.energyos.espi.common.domain.DateTimeInterval;
import org.energyos.espi.common.domain.RetailCustomer;
import org.energyos.espi.common.domain.Routes;
import org.energyos.espi.common.domain.Subscription;
import org.energyos.espi.common.domain.UsagePoint;
import org.energyos.espi.common.service.ApplicationInformationService;
import org.energyos.espi.common.service.AuthorizationService;
import org.energyos.espi.common.service.ResourceService;
import org.energyos.espi.common.service.RetailCustomerService;
import org.energyos.espi.common.service.SubscriptionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.transaction.annotation.Transactional;
public class EspiTokenEnhancer implements TokenEnhancer {
@Autowired
private ApplicationInformationService applicationInformationService;
@Autowired
private SubscriptionService subscriptionService;
@Autowired
private ResourceService resourceService;
@Autowired
private AuthorizationService authorizationService;
@Autowired
private RetailCustomerService retailCustomerService;
@Transactional(rollbackFor = { javax.xml.bind.JAXBException.class }, noRollbackFor = {
javax.persistence.NoResultException.class,
org.springframework.dao.EmptyResultDataAccessException.class })
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(
accessToken);
System.out.printf("EspiTokenEnhancer: OAuth2Request Parameters = %s\n",
authentication.getOAuth2Request().getRequestParameters());
System.out.printf("EspiTokenEnhancer: Authorities = %s\n",
authentication.getAuthorities());
String clientId = authentication.getOAuth2Request().getClientId();
ApplicationInformation ai = null;
// [mjb20150102] Allow REGISTRATION_xxxx and ADMIN_xxxx to use same
// ApplicationInformation record
String ci = clientId;
String clientCredentialsScope = accessToken.getScope().toString();
if (ci.indexOf("REGISTRATION_") != -1) {
if (ci.substring(0, "REGISTRATION_".length()).equals(
"REGISTRATION_")) {
ci = ci.substring("REGISTRATION_".length());
}
}
if (ci.indexOf("_admin") != -1) {
ci = ci.substring(0, ci.indexOf("_admin"));
}
// Confirm Application Information record exists for ClientID requesting
// an access token
try {
ai = applicationInformationService.findByClientId(ci);
} catch (NoResultException | EmptyResultDataAccessException e) {
System.out.printf(
"\nEspiTokenEnhancer: ApplicationInformation record not found!\n"
+ "OAuth2Request Parameters = %s\n", authentication
.getOAuth2Request().getRequestParameters()
+ " client_id = " + clientId);
throw new AccessDeniedException(String.format(
"No client with requested id: %s", clientId));
}
Map<String, String> requestParameters = authentication
.getOAuth2Request().getRequestParameters();
String grantType = requestParameters.get(OAuth2Utils.GRANT_TYPE);
grantType = grantType.toLowerCase();
// Is this a "client_credentials" access token grant_type request?
if (grantType.contentEquals("client_credentials")) {
// Processing a "client_credentials" access token grant_type
// request.
// Reject a client_credentials request if Authority equals
// "ROLE_USER"
if (authentication.getAuthorities().toString()
.contains("[ROLE_USER]")) {
throw new InvalidGrantException(
String.format("Client Credentials not valid for ROLE_USER\n"));
}
// Create Authorization and add authorizationURI to /oath/token
// response
Authorization authorization = authorizationService
.createAuthorization(null, result.getValue());
result.getAdditionalInformation().put(
"authorizationURI",
ai.getDataCustodianResourceEndpoint()
+ Routes.DATA_CUSTODIAN_AUTHORIZATION.replace(
"espi/1_1/resource/", "").replace(
"{authorizationId}",
authorization.getId().toString()));
// Create Subscription
Subscription subscription = subscriptionService
.createSubscription(authentication);
// Initialize Authorization record
authorization.setThirdParty(authentication.getOAuth2Request()
.getClientId());
authorization.setAccessToken(accessToken.getValue());
authorization.setTokenType(accessToken.getTokenType());
authorization.setExpiresIn((long) accessToken.getExpiresIn());
authorization.setAuthorizedPeriod(new DateTimeInterval((long) 0,
(long) 0));
authorization.setPublishedPeriod(new DateTimeInterval((long) 0,
(long) 0));
if (accessToken.getRefreshToken() != null) {
authorization.setRefreshToken(accessToken.getRefreshToken()
.toString());
}
// Remove "[" and "]" surrounding Scope in accessToken structure
authorization.setScope(accessToken
.getScope()
.toString()
.substring(1,
(accessToken.getScope().toString().length() - 1)));
// set the authorizationUri
authorization.setAuthorizationURI(ai
.getDataCustodianResourceEndpoint()
+ Routes.DATA_CUSTODIAN_AUTHORIZATION.replace(
"espi/1_1/resource/", "").replace(
"{authorizationId}",
authorization.getId().toString()));
// Determine resourceURI value based on Client's Role
Set<String> role = AuthorityUtils.authorityListToSet(authentication
.getAuthorities());
if (role.contains("ROLE_DC_ADMIN")) {
authorization.setResourceURI(ai
.getDataCustodianResourceEndpoint() + "/");
} else {
if (role.contains("ROLE_TP_ADMIN")) {
authorization.setResourceURI(ai
.getDataCustodianResourceEndpoint()
+ Routes.BATCH_BULK_MEMBER.replace(
"espi/1_1/resource/", "").replace(
"{bulkId}", "**"));
} else {
if (role.contains("ROLE_UL_ADMIN")) {
authorization.setResourceURI(ai
.getDataCustodianResourceEndpoint()
+ Routes.BATCH_UPLOAD_MY_DATA.replace(
"espi/1_1/resource/", "").replace(
"{retailCustomerId}", "**"));
} else {
if (role.contains("ROLE_TP_REGISTRATION")) {
authorization
.setResourceURI(ai
.getDataCustodianResourceEndpoint()
+ Routes.ROOT_APPLICATION_INFORMATION_MEMBER
.replace(
"espi/1_1/resource/",
"")
.replace(
"{applicationInformationId}",
ai.getId()
.toString()));
}
}
}
}
authorization
.setApplicationInformation(applicationInformationService
.findByClientId(ci));
authorization.setRetailCustomer(retailCustomerService
.findById((long) 0));
authorization.setUpdated(new GregorianCalendar());
authorization.setStatus("1"); // Set authorization record status as
// "Active"
authorization.setSubscription(subscription);
authorizationService.merge(authorization);
// Add resourceURI to access_token response
result.getAdditionalInformation().put("resourceURI",
authorization.getResourceURI());
// Initialize Subscription record
subscription.setAuthorization(authorization);
subscription.setUpdated(new GregorianCalendar());
subscriptionService.merge(subscription);
} else if (grantType.contentEquals("authorization_code")) {
try {
// Is this a refresh_token grant_type request?
Authorization authorization = authorizationService
.findByRefreshToken(result.getRefreshToken().getValue());
// Yes, update access token
authorization.setAccessToken(accessToken.getValue());
authorizationService.merge(authorization);
// Add ResourceURI and AuthorizationURI to access_token response
result.getAdditionalInformation().put("resourceURI",
authorization.getResourceURI());
result.getAdditionalInformation().put("authorizationURI",
authorization.getAuthorizationURI());
} catch (NoResultException | EmptyResultDataAccessException e) {
// No, process as initial access token request
// Create Subscription and add resourceURI to /oath/token
// response
Subscription subscription = subscriptionService
.createSubscription(authentication);
result.getAdditionalInformation().put(
"resourceURI",
ai.getDataCustodianResourceEndpoint()
+ Routes.BATCH_SUBSCRIPTION.replace(
"espi/1_1/resource/", "").replace(
"{subscriptionId}",
subscription.getId().toString()));
// Create Authorization and add authorizationURI to /oath/token
// response
Authorization authorization = authorizationService
.createAuthorization(subscription, result.getValue());
result.getAdditionalInformation().put(
"authorizationURI",
ai.getDataCustodianResourceEndpoint()
+ Routes.DATA_CUSTODIAN_AUTHORIZATION.replace(
"espi/1_1/resource/", "").replace(
"{authorizationId}",
authorization.getId().toString()));
// Update Data Custodian subscription structure
subscription.setAuthorization(authorization);
subscription.setUpdated(new GregorianCalendar());
subscriptionService.merge(subscription);
RetailCustomer retailCustomer = (RetailCustomer) authentication
.getPrincipal();
// link in the usage points associated with this subscription
List<Long> usagePointIds = resourceService.findAllIdsByXPath(
retailCustomer.getId(), UsagePoint.class);
Iterator<Long> it = usagePointIds.iterator();
while (it.hasNext()) {
UsagePoint up = resourceService.findById(it.next(),
UsagePoint.class);
up.setSubscription(subscription);
resourceService.persist(up); // maybe not needed??
}
// Update Data Custodian authorization structure
authorization
.setApplicationInformation(applicationInformationService
.findByClientId(authentication
.getOAuth2Request().getClientId()));
authorization.setThirdParty(authentication.getOAuth2Request()
.getClientId());
authorization.setRetailCustomer(retailCustomer);
authorization.setAccessToken(accessToken.getValue());
authorization.setTokenType(accessToken.getTokenType());
authorization.setExpiresIn((long) accessToken.getExpiresIn());
if (accessToken.getRefreshToken() != null) {
authorization.setRefreshToken(accessToken.getRefreshToken()
.toString());
}
// Remove "[" and "]" surrounding Scope in accessToken structure
authorization
.setScope(accessToken
.getScope()
.toString()
.substring(
1,
(accessToken.getScope().toString()
.length() - 1)));
authorization.setAuthorizationURI(ai
.getDataCustodianResourceEndpoint()
+ Routes.DATA_CUSTODIAN_AUTHORIZATION.replace(
"espi/1_1/resource/", "").replace(
"{authorizationId}",
authorization.getId().toString()));
authorization.setResourceURI(ai
.getDataCustodianResourceEndpoint()
+ Routes.BATCH_SUBSCRIPTION.replace(
"espi/1_1/resource/", "").replace(
"{subscriptionId}",
subscription.getId().toString()));
authorization.setUpdated(new GregorianCalendar());
authorization.setStatus("1"); // Set authorization record status
// as "Active"
authorization.setSubscription(subscription);
authorization.setAuthorizedPeriod(new DateTimeInterval(
(long) 0, (long) 0));
authorization.setPublishedPeriod(new DateTimeInterval((long) 0,
(long) 0));
authorizationService.merge(authorization);
}
} else {
System.out
.printf("EspiTokenEnhancer: Invalid Grant_Type processed by Spring Security OAuth2 Framework:\n"
+ "OAuth2Request Parameters = %s\n", authentication
.getOAuth2Request().getRequestParameters());
throw new AccessDeniedException(
String.format("Unsupported ESPI OAuth2 grant_type"));
}
return result;
}
public void setApplicationInformationService(
ApplicationInformationService applicationInformationService) {
this.applicationInformationService = applicationInformationService;
}
public ApplicationInformationService getApplicationInformationService() {
return this.applicationInformationService;
}
public void setSubscriptionService(SubscriptionService subscriptionService) {
this.subscriptionService = subscriptionService;
}
public SubscriptionService getSubscriptionService() {
return this.subscriptionService;
}
public void setResourceService(ResourceService resourceService) {
this.resourceService = resourceService;
}
public ResourceService getResourceService() {
return this.resourceService;
}
public void setAuthorizationService(
AuthorizationService authorizationService) {
this.authorizationService = authorizationService;
}
public AuthorizationService getAuthorizationService() {
return this.authorizationService;
}
}