/* * 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.thirdparty.web; import java.security.Principal; import java.util.GregorianCalendar; import javax.persistence.NoResultException; import javax.xml.bind.JAXBException; import org.energyos.espi.common.domain.AccessToken; import org.energyos.espi.common.domain.ApplicationInformation; import org.energyos.espi.common.domain.Authorization; import org.energyos.espi.common.domain.RetailCustomer; import org.energyos.espi.common.domain.Routes; import org.energyos.espi.common.service.AuthorizationService; import org.energyos.espi.thirdparty.repository.UsagePointRESTRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.client.HttpClientErrorException; @Controller public class AuthorizationController extends BaseController { @Autowired private AuthorizationService authorizationService; // TODO The following is legacy code that will do the import of a // subscription // from a DC. Needs to be separated out of the "repository" level and be // callable as a TP specific service level. @Autowired private UsagePointRESTRepository usagePointRESTRepository; @Autowired @Qualifier("clientRestTemplateFactory") private ClientRestTemplateFactory templateFactory; @RequestMapping(value = Routes.THIRD_PARTY_OAUTH_CODE_CALLBACK, method = RequestMethod.GET) public String authorization( String code, String state, ModelMap model, Principal principal, @RequestParam(value = "error", required = false) String error, @RequestParam(value = "error_description", required = false) String error_description, @RequestParam(value = "error_uri", required = false) String error_uri) { try { // Is /oauth/authorization response valid (i.e. is the "state" // element correct)? Authorization authorization = authorizationService .findByState(state); // Process valid /oauth/authorization response ApplicationInformation applicationInformation = authorization .getApplicationInformation(); // Verify /oauth/authorization Endpoint process completed // successfully if (code != null) { try { // Update Authorization record with returned authorization // code for audit purposes authorization.setCode(code); authorization.setGrantType("authorization_code"); authorization.setUpdated(new GregorianCalendar()); authorizationService.merge(authorization); // Format /oauth/token Endpoint request String url = String .format("%s?redirect_uri=%s&code=%s&grant_type=authorization_code", applicationInformation .getAuthorizationServerTokenEndpoint(), applicationInformation.getRedirectUri(), code); // Build /oauth/token Endpoint request ClientRestTemplate restTemplate = templateFactory .newClientRestTemplate( applicationInformation.getClientId(), applicationInformation.getClientSecret()); // Issue /oauth/token Endpoint request AccessToken token = restTemplate.getForObject(url, AccessToken.class); // Process /oauth/token Endpoint response if (token.getAccessToken() != null) { authorization.setAccessToken(token.getAccessToken()); authorization.setTokenType(token.getTokenType()); authorization.setExpiresIn(token.getExpiresIn()); authorization.setRefreshToken(token.getRefreshToken()); authorization.setScope(token.getScope()); authorization.setAuthorizationURI(token .getAuthorizationURI()); authorization.setResourceURI(token.getResourceURI()); authorization.setUpdated(new GregorianCalendar()); authorization.setStatus("1"); // Set authorization // record status as // "Active" authorization.setState(null); // Clear State as a // security measure // Update authorization record with /oauth/token // response data authorizationService.merge(authorization); // now do the initial import of the Authorized Resource, // if it is // not ready, then we will wait till we receive a Notify // or the UX call for it. // TODO: create a Subscription to work with if needed RetailCustomer currentCustomer = currentCustomer(principal); try { usagePointRESTRepository .findAllByRetailCustomerId(currentCustomer .getId()); } catch (JAXBException e) { // nothing there, so log the fact and move on. It // will get imported later. System.out.printf( "\nThirdParty Import Exception: %s\n", e.toString()); e.printStackTrace(); } } else { System.out .printf("\n/oauth/token Request did not return an access token\n"); } } catch (HttpClientErrorException x) { // TODO: Extract error, error_description and error_uri from // JSON response. Currently recording null for all three // fields. // Update authorization record System.out.printf("\nHTTPClientException: %s\n", x.toString()); authorization.setError(error); authorization.setErrorDescription(error_description); authorization.setErrorUri(error_uri); authorization.setUpdated(new GregorianCalendar()); authorization.setStatus("2"); // Set authorization record // status as "Denied" authorization.setState(null); // Clear State as a security // measure authorizationService.merge(authorization); // TODO: Should the "message" differ based on the exception? throw new UserDeniedAuthorizationException( "Unable to retrieve OAuth token", x); } } else { System.out .printf("\nOAuth2 authorization_request returned an error:\n"); System.out.printf("Error: " + error + "\n"); System.out.printf("Error_description: " + error_description + "\n"); System.out.printf("Error_uri: " + error_uri + "\n"); // Update authorization record with error response authorization.setError(error); authorization.setErrorDescription(error_description); authorization.setErrorUri(error_uri); authorization.setUpdated(new GregorianCalendar()); authorization.setStatus("2"); // Set authorization record status // as "Denied" authorization.setState(null); // Clear State as a security // measure authorizationService.merge(authorization); throw new UserDeniedAuthorizationException("Error: " + error_description); } } catch (NoResultException | EmptyResultDataAccessException e) { // We received an invalid /oauth/authorization response // TODO: Log receipt of an invalid /oauth/authorization response return "/home"; } return "redirect:/RetailCustomer/" + currentCustomer(principal).getId() + "/AuthorizationList"; } @RequestMapping(value = Routes.THIRD_PARTY_AUTHORIZATION, method = RequestMethod.GET) public String index(ModelMap model, Authentication principal) { model.put( "authorizationList", authorizationService.findAllByRetailCustomerId(currentCustomer( principal).getId())); return "/RetailCustomer/AuthorizationList/index"; } public void setAuthorizationService( AuthorizationService authorizationService) { this.authorizationService = authorizationService; } public AuthorizationService getAuthorizationService() { return this.authorizationService; } public void setUsagePointRESTRepository( UsagePointRESTRepository usagePointRESTRepository) { this.usagePointRESTRepository = usagePointRESTRepository; } public UsagePointRESTRepository getUsagePointRESTRepository() { return this.usagePointRESTRepository; } public void setClientRestTemplateFactory( ClientRestTemplateFactory templateFactory) { this.templateFactory = templateFactory; } public ClientRestTemplateFactory getClientRestTemplateFactory() { return this.templateFactory; } }