001/******************************************************************************* 002 * Copyright 2017 The MIT Internet Trust Consortium 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 *******************************************************************************/ 016 017package org.mitre.uma.web; 018 019import java.util.Set; 020 021import org.mitre.oauth2.model.ClientDetailsEntity; 022import org.mitre.oauth2.service.ClientDetailsEntityService; 023import org.mitre.openid.connect.model.OIDCAuthenticationToken; 024import org.mitre.openid.connect.model.UserInfo; 025import org.mitre.openid.connect.view.HttpCodeView; 026import org.mitre.uma.model.Claim; 027import org.mitre.uma.model.PermissionTicket; 028import org.mitre.uma.service.PermissionService; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031import org.springframework.beans.factory.annotation.Autowired; 032import org.springframework.http.HttpStatus; 033import org.springframework.security.access.prepost.PreAuthorize; 034import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; 035import org.springframework.stereotype.Controller; 036import org.springframework.ui.Model; 037import org.springframework.web.bind.annotation.RequestMapping; 038import org.springframework.web.bind.annotation.RequestMethod; 039import org.springframework.web.bind.annotation.RequestParam; 040import org.springframework.web.util.UriComponentsBuilder; 041 042import com.google.common.base.Strings; 043import com.google.common.collect.Sets; 044import com.google.gson.JsonElement; 045import com.google.gson.JsonPrimitive; 046 047/** 048 * 049 * Collect claims interactively from the end user. 050 * 051 * @author jricher 052 * 053 */ 054@Controller 055@PreAuthorize("hasRole('ROLE_EXTERNAL_USER')") 056@RequestMapping("/" + ClaimsCollectionEndpoint.URL) 057public class ClaimsCollectionEndpoint { 058 // Logger for this class 059 private static final Logger logger = LoggerFactory.getLogger(ClaimsCollectionEndpoint.class); 060 061 public static final String URL = "rqp_claims"; 062 063 @Autowired 064 private ClientDetailsEntityService clientService; 065 066 @Autowired 067 private PermissionService permissionService; 068 069 070 @RequestMapping(method = RequestMethod.GET) 071 public String collectClaims(@RequestParam("client_id") String clientId, @RequestParam(value = "redirect_uri", required = false) String redirectUri, 072 @RequestParam("ticket") String ticketValue, @RequestParam(value = "state", required = false) String state, 073 Model m, OIDCAuthenticationToken auth) { 074 075 076 ClientDetailsEntity client = clientService.loadClientByClientId(clientId); 077 078 PermissionTicket ticket = permissionService.getByTicket(ticketValue); 079 080 if (client == null || ticket == null) { 081 logger.info("Client or ticket not found: " + clientId + " :: " + ticketValue); 082 m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 083 return HttpCodeView.VIEWNAME; 084 } 085 086 // we've got a client and ticket, let's attach the claims that we have from the token and userinfo 087 088 // subject 089 Set<Claim> claimsSupplied = Sets.newHashSet(ticket.getClaimsSupplied()); 090 091 String issuer = auth.getIssuer(); 092 UserInfo userInfo = auth.getUserInfo(); 093 094 claimsSupplied.add(mkClaim(issuer, "sub", new JsonPrimitive(auth.getSub()))); 095 if (userInfo.getEmail() != null) { 096 claimsSupplied.add(mkClaim(issuer, "email", new JsonPrimitive(userInfo.getEmail()))); 097 } 098 if (userInfo.getEmailVerified() != null) { 099 claimsSupplied.add(mkClaim(issuer, "email_verified", new JsonPrimitive(userInfo.getEmailVerified()))); 100 } 101 if (userInfo.getPhoneNumber() != null) { 102 claimsSupplied.add(mkClaim(issuer, "phone_number", new JsonPrimitive(auth.getUserInfo().getPhoneNumber()))); 103 } 104 if (userInfo.getPhoneNumberVerified() != null) { 105 claimsSupplied.add(mkClaim(issuer, "phone_number_verified", new JsonPrimitive(auth.getUserInfo().getPhoneNumberVerified()))); 106 } 107 if (userInfo.getPreferredUsername() != null) { 108 claimsSupplied.add(mkClaim(issuer, "preferred_username", new JsonPrimitive(auth.getUserInfo().getPreferredUsername()))); 109 } 110 if (userInfo.getProfile() != null) { 111 claimsSupplied.add(mkClaim(issuer, "profile", new JsonPrimitive(auth.getUserInfo().getProfile()))); 112 } 113 114 ticket.setClaimsSupplied(claimsSupplied); 115 116 PermissionTicket updatedTicket = permissionService.updateTicket(ticket); 117 118 if (Strings.isNullOrEmpty(redirectUri)) { 119 if (client.getClaimsRedirectUris().size() == 1) { 120 redirectUri = client.getClaimsRedirectUris().iterator().next(); // get the first (and only) redirect URI to use here 121 logger.info("No redirect URI passed in, using registered value: " + redirectUri); 122 } else { 123 throw new RedirectMismatchException("Unable to find redirect URI and none passed in."); 124 } 125 } else { 126 if (!client.getClaimsRedirectUris().contains(redirectUri)) { 127 throw new RedirectMismatchException("Claims redirect did not match the registered values."); 128 } 129 } 130 131 UriComponentsBuilder template = UriComponentsBuilder.fromUriString(redirectUri); 132 template.queryParam("authorization_state", "claims_submitted"); 133 if (!Strings.isNullOrEmpty(state)) { 134 template.queryParam("state", state); 135 } 136 137 String uriString = template.toUriString(); 138 logger.info("Redirecting to " + uriString); 139 140 return "redirect:" + uriString; 141 } 142 143 144 private Claim mkClaim(String issuer, String name, JsonElement value) { 145 Claim c = new Claim(); 146 c.setIssuer(Sets.newHashSet(issuer)); 147 c.setName(name); 148 c.setValue(value); 149 return c; 150 } 151 152}