package de.passau.uni.sec.compose.id.rest.controller; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import de.passau.uni.sec.compose.id.common.exception.IdManagementException; import de.passau.uni.sec.compose.id.core.persistence.entities.Code; import de.passau.uni.sec.compose.id.core.persistence.repository.ServiceInstanceRepository; import de.passau.uni.sec.compose.id.core.service.CodeService; import de.passau.uni.sec.compose.id.core.service.ServiceInstanceService; import de.passau.uni.sec.compose.id.core.service.security.RestAuthentication; import de.passau.uni.sec.compose.id.core.service.security.TokenResponse; import de.passau.uni.sec.compose.id.core.service.security.UsersAuthzAndAuthClient; import de.passau.uni.sec.compose.id.rest.messages.UserAuthenticatedMessage; @Controller public class Oauth2 { private static Logger LOG = LoggerFactory.getLogger(Oauth2.class); // GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb @Autowired UsersAuthzAndAuthClient uaaClient; @Autowired CodeService codeService; @Autowired RestAuthentication restAuth; @Autowired ServiceInstanceRepository serviceInstanceRepo; /** * implicit grant! respond with the credentials as a URL fragment * @param resposeType * @param clientId * @param state * @param url * @param model * @return */ @RequestMapping(value={"/authorize","oauth/authorize"}) public String authorize(@RequestParam(value="response_type", required=false, defaultValue="token") String resposeType, @RequestParam(value="client_id", required=false, defaultValue="clientId") String clientId, @RequestParam(value="state", required=false, defaultValue="pre") String state, @RequestParam(value="redirect_uri", required=true) String url, @RequestParam(value="error", required=false, defaultValue="") String error, Model model) { // GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz LOG.info("Calling authorization endpoint from: "+url+" with clientId: "+clientId+" and responseType: "+resposeType); model.addAttribute("response_type", resposeType); model.addAttribute("client_id", clientId); model.addAttribute("state", state); model.addAttribute("redirect_uri", url); if(error!= null && !"".equals(error)) { model.addAttribute("error", error); } return "login"; } @RequestMapping(value ={"/login","/oauth/login"}, method = RequestMethod.POST) public String login( @RequestParam(value="response_type", required=true, defaultValue="token") String resposeType, @RequestParam(value="client_id", required=true) String clientId, @RequestParam(value="state", required=false, defaultValue="pre") String state, @RequestParam(value="redirect_uri", required=true) String url, @RequestParam(value="username", required=true) String username, @RequestParam(value="password", required=true) String password, Model model) { LOG.info("Posting credentials to Login controller with redirect_url: "+url+" with clientId: "+clientId+" and responseType: "+resposeType+" and username: "+username); TokenResponse res; try { res = uaaClient.getImplicitTokenCredentials(null, username, password); if(resposeType.toUpperCase().equals("CODE")) { String code = generateCode(); codeService.addCode(code, encodeOauthRequestData(username, password,clientId), CodeService.TYPE_OAUTH2_CODE); //TODO in the future this could be actually a code instead of a token?? return "redirect:" + url+"?code="+code; } else{ return "redirect:" + url+"#access_token="+res.getAccessToken()+"&state="+state+"&token_type=bearer&expires_in=3600"; } } catch (IdManagementException e) { model.addAttribute("loginError", true); model.addAttribute("response_type", resposeType); model.addAttribute("client_id", clientId); model.addAttribute("state", state); model.addAttribute("redirect_uri", url); model.addAttribute("username", username); model.addAttribute("password",password); //TODO change this! do not redirect... reload idm page. return "login"; } /* * HTTP/1.1 302 Found Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA &state=xyz&token_type=example&expires_in=3600 */ /* ERROR url * HTTP/1.1 302 Found Location: https://client.example.com/cb#error=access_denied&state=xyz */ } /* * POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=password&username=johndoe&password=A3ddj3w * */ private String generateCode() { return UUID.randomUUID().toString().replaceAll("-","").substring(0,10); } @RequestMapping(value = {"token","oauth/token"}, method = RequestMethod.POST, produces="application/json") public ResponseEntity<HashMap<String, Object>> token(@RequestHeader("Authorization") String clientAuthentication, @RequestParam(value="grant_type", required=true, defaultValue="authorization_code") String grantType, @RequestParam(value="code", required=true, defaultValue="clientId") String code, @RequestParam(value="redirect_uri", required=true) String url, Model model) throws IdManagementException { LOG.info("Retrieving token for auth code: "+code+" with redirect_url: "+url+" with Authorization heder: "+clientAuthentication); HashMap<String, Object> map = new HashMap<>(); //TODO at some point we could verify authenticatoin of client?? with the Authorization header? if(grantType.equals("authorization_code")) { Code tokenCode = codeService.getCode(code, CodeService.TYPE_OAUTH2_CODE); if(tokenCode!=null) { List<String> credentials = decodeOauth2RequestData(tokenCode.getReference()); TokenResponse res = uaaClient.getImplicitTokenCredentials(null, credentials.get(0), credentials.get(1)); map.put("access_token",res.getAccessToken()); map.put("token_type","bearer"); map.put("expires_in",3600); HttpHeaders headers = new HttpHeaders(); headers.add("Cache-Control", "no-store"); headers.add("Pragma","no-cache"); codeService.deleteCode(tokenCode); return new ResponseEntity<>(map, headers, HttpStatus.OK); } } //TODO check this... return new ResponseEntity<>(map, HttpStatus.UNAUTHORIZED); /* { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"example", "expires_in":3600, "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", "example_parameter":"example_value" }*/ } //grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb private String encodeOauthRequestData(String username, String password, String clientId) { return username+"#!"+password+"#!"+clientId; } private List<String> decodeOauth2RequestData(String reference) { String[] arr = reference.split("#!"); return Arrays.asList(arr); } }