001/******************************************************************************* 002 * Copyright 2017 The MIT Internet Trust Consortium 003 * 004 * Portions copyright 2011-2013 The MITRE Corporation 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 *******************************************************************************/ 018package org.mitre.openid.connect.web; 019 020import java.util.List; 021 022import org.mitre.oauth2.model.ClientDetailsEntity; 023import org.mitre.oauth2.service.ClientDetailsEntityService; 024import org.mitre.oauth2.service.SystemScopeService; 025import org.mitre.openid.connect.model.UserInfo; 026import org.mitre.openid.connect.service.UserInfoService; 027import org.mitre.openid.connect.view.HttpCodeView; 028import org.mitre.openid.connect.view.UserInfoJWTView; 029import org.mitre.openid.connect.view.UserInfoView; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032import org.springframework.beans.factory.annotation.Autowired; 033import org.springframework.http.HttpHeaders; 034import org.springframework.http.HttpStatus; 035import org.springframework.http.MediaType; 036import org.springframework.security.access.prepost.PreAuthorize; 037import org.springframework.security.oauth2.provider.OAuth2Authentication; 038import org.springframework.stereotype.Controller; 039import org.springframework.ui.Model; 040import org.springframework.web.bind.annotation.RequestHeader; 041import org.springframework.web.bind.annotation.RequestMapping; 042import org.springframework.web.bind.annotation.RequestMethod; 043import org.springframework.web.bind.annotation.RequestParam; 044 045import com.google.common.base.Strings; 046 047/** 048 * OpenID Connect UserInfo endpoint, as specified in Standard sec 5 and Messages sec 2.4. 049 * 050 * @author AANGANES 051 * 052 */ 053@Controller 054@RequestMapping("/" + UserInfoEndpoint.URL) 055public class UserInfoEndpoint { 056 057 public static final String URL = "userinfo"; 058 059 @Autowired 060 private UserInfoService userInfoService; 061 062 @Autowired 063 private ClientDetailsEntityService clientService; 064 065 /** 066 * Logger for this class 067 */ 068 private static final Logger logger = LoggerFactory.getLogger(UserInfoEndpoint.class); 069 070 /** 071 * Get information about the user as specified in the accessToken included in this request 072 */ 073 @PreAuthorize("hasRole('ROLE_USER') and #oauth2.hasScope('" + SystemScopeService.OPENID_SCOPE + "')") 074 @RequestMapping(method= {RequestMethod.GET, RequestMethod.POST}, produces = {MediaType.APPLICATION_JSON_VALUE, UserInfoJWTView.JOSE_MEDIA_TYPE_VALUE}) 075 public String getInfo(@RequestParam(value="claims", required=false) String claimsRequestJsonString, 076 @RequestHeader(value=HttpHeaders.ACCEPT, required=false) String acceptHeader, 077 OAuth2Authentication auth, Model model) { 078 079 if (auth == null) { 080 logger.error("getInfo failed; no principal. Requester is not authorized."); 081 model.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); 082 return HttpCodeView.VIEWNAME; 083 } 084 085 String username = auth.getName(); 086 UserInfo userInfo = userInfoService.getByUsernameAndClientId(username, auth.getOAuth2Request().getClientId()); 087 088 if (userInfo == null) { 089 logger.error("getInfo failed; user not found: " + username); 090 model.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 091 return HttpCodeView.VIEWNAME; 092 } 093 094 model.addAttribute(UserInfoView.SCOPE, auth.getOAuth2Request().getScope()); 095 096 model.addAttribute(UserInfoView.AUTHORIZED_CLAIMS, auth.getOAuth2Request().getExtensions().get("claims")); 097 098 if (!Strings.isNullOrEmpty(claimsRequestJsonString)) { 099 model.addAttribute(UserInfoView.REQUESTED_CLAIMS, claimsRequestJsonString); 100 } 101 102 model.addAttribute(UserInfoView.USER_INFO, userInfo); 103 104 // content negotiation 105 106 // start off by seeing if the client has registered for a signed/encrypted JWT from here 107 ClientDetailsEntity client = clientService.loadClientByClientId(auth.getOAuth2Request().getClientId()); 108 model.addAttribute(UserInfoJWTView.CLIENT, client); 109 110 List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader); 111 MediaType.sortBySpecificityAndQuality(mediaTypes); 112 113 if (client.getUserInfoSignedResponseAlg() != null 114 || client.getUserInfoEncryptedResponseAlg() != null 115 || client.getUserInfoEncryptedResponseEnc() != null) { 116 // client has a preference, see if they ask for plain JSON specifically on this request 117 for (MediaType m : mediaTypes) { 118 if (!m.isWildcardType() && m.isCompatibleWith(UserInfoJWTView.JOSE_MEDIA_TYPE)) { 119 return UserInfoJWTView.VIEWNAME; 120 } else if (!m.isWildcardType() && m.isCompatibleWith(MediaType.APPLICATION_JSON)) { 121 return UserInfoView.VIEWNAME; 122 } 123 } 124 125 // otherwise return JWT 126 return UserInfoJWTView.VIEWNAME; 127 } else { 128 // client has no preference, see if they asked for JWT specifically on this request 129 for (MediaType m : mediaTypes) { 130 if (!m.isWildcardType() && m.isCompatibleWith(MediaType.APPLICATION_JSON)) { 131 return UserInfoView.VIEWNAME; 132 } else if (!m.isWildcardType() && m.isCompatibleWith(UserInfoJWTView.JOSE_MEDIA_TYPE)) { 133 return UserInfoJWTView.VIEWNAME; 134 } 135 } 136 137 // otherwise return JSON 138 return UserInfoView.VIEWNAME; 139 } 140 141 } 142 143}