001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * 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 distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk; 019 020 021import java.net.URI; 022import java.util.Collections; 023import java.util.List; 024import java.util.Map; 025 026import net.jcip.annotations.Immutable; 027 028import com.nimbusds.common.contenttype.ContentType; 029import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; 030import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; 031import com.nimbusds.oauth2.sdk.http.HTTPRequest; 032import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 033import com.nimbusds.oauth2.sdk.util.StringUtils; 034import com.nimbusds.oauth2.sdk.util.URLUtils; 035import com.nimbusds.openid.connect.sdk.AuthenticationRequest; 036import com.nimbusds.openid.connect.sdk.op.AuthenticationRequestDetector; 037 038 039/** 040 * Pushed authorisation request. 041 * 042 * <p>Example HTTP request: 043 * 044 * <pre> 045 * POST /as/par HTTP/1.1 046 * Host: as.example.com 047 * Content-Type: application/x-www-form-urlencoded 048 * Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3 049 * 050 * response_type=code 051 * &client_id=s6BhdRkqt3&state=af0ifjsldkj 052 * &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb 053 * </pre> 054 * 055 * <p>Related specifications: 056 * 057 * <ul> 058 * <li>OAuth 2.0 Pushed Authorization Requests (draft-ietf-oauth-par-02) 059 * </ul> 060 */ 061@Immutable 062public class PushedAuthorizationRequest extends AbstractOptionallyAuthenticatedRequest { 063 064 065 /** 066 * The pushed authorisation request. 067 */ 068 private final AuthorizationRequest authzRequest; 069 070 071 /** 072 * Creates a new authenticated pushed authorisation request for a 073 * confidential client. 074 * 075 * @param uri The URI of the token endpoint. May be 076 * {@code null} if the {@link #toHTTPRequest} 077 * method will not be used. 078 * @param clientAuth The client authentication. Must not be 079 * {@code null}. 080 * @param authzRequest The authorisation request. Must not be 081 * {@code null}. 082 */ 083 public PushedAuthorizationRequest(final URI uri, 084 final ClientAuthentication clientAuth, 085 final AuthorizationRequest authzRequest) { 086 super(uri, clientAuth); 087 088 if (clientAuth == null) 089 throw new IllegalArgumentException("The client authentication must not be null"); 090 091 if (authzRequest == null) { 092 throw new IllegalArgumentException("The authorization request must not be null"); 093 } 094 if (authzRequest.getRequestURI() != null) { 095 throw new IllegalArgumentException("Authorization request_uri parameter not allowed"); 096 } 097 this.authzRequest = authzRequest; 098 } 099 100 101 /** 102 * Creates a new pushed authorisation request for a public client. 103 * 104 * @param uri The URI of the token endpoint. May be 105 * {@code null} if the {@link #toHTTPRequest} 106 * method will not be used. 107 * @param authzRequest The authorisation request. Must not be 108 * {@code null}. 109 */ 110 public PushedAuthorizationRequest(final URI uri, 111 final AuthorizationRequest authzRequest) { 112 113 super(uri, null); 114 if (authzRequest == null) { 115 throw new IllegalArgumentException("The authorization request must not be null"); 116 } 117 if (authzRequest.getRequestURI() != null) { 118 throw new IllegalArgumentException("Authorization request_uri parameter not allowed"); 119 } 120 this.authzRequest = authzRequest; 121 } 122 123 124 /** 125 * Returns the pushed authorisation request. 126 * 127 * @return The pushed authorisation request. 128 */ 129 public AuthorizationRequest getAuthorizationRequest() { 130 return authzRequest; 131 } 132 133 134 @Override 135 public HTTPRequest toHTTPRequest() { 136 137 if (getEndpointURI() == null) 138 throw new SerializeException("The endpoint URI is not specified"); 139 140 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI()); 141 httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED); 142 143 if (getClientAuthentication() != null) { 144 getClientAuthentication().applyTo(httpRequest); 145 } 146 147 Map<String, List<String>> params = httpRequest.getQueryParameters(); 148 params.putAll(authzRequest.toParameters()); 149 httpRequest.setQuery(URLUtils.serializeParameters(params)); 150 151 return httpRequest; 152 } 153 154 155 /** 156 * Parses a pushed authorisation request from the specified HTTP 157 * request. 158 * 159 * @param httpRequest The HTTP request. Must not be {@code null}. 160 * 161 * @return The pushed authorisation request. 162 * 163 * @throws ParseException If the HTTP request couldn't be parsed to a 164 * pushed authorisation request. 165 */ 166 public static PushedAuthorizationRequest parse(final HTTPRequest httpRequest) 167 throws ParseException { 168 169 // Only HTTP POST accepted 170 URI uri = httpRequest.getURI(); 171 httpRequest.ensureMethod(HTTPRequest.Method.POST); 172 httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED); 173 174 // Parse client authentication, if any 175 ClientAuthentication clientAuth; 176 try { 177 clientAuth = ClientAuthentication.parse(httpRequest); 178 } catch (ParseException e) { 179 throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage())); 180 } 181 182 // No fragment! May use query component! 183 Map<String,List<String>> params = httpRequest.getQueryParameters(); 184 185 // Multiple conflicting client auth methods (issue #203)? 186 if (clientAuth instanceof ClientSecretBasic) { 187 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) || StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) { 188 String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion"; 189 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 190 } 191 } 192 193 // client_id not required in authZ params if auth is present 194 if (! params.containsKey("client_id") && clientAuth != null) { 195 params.put("client_id", Collections.singletonList(clientAuth.getClientID().getValue())); 196 } 197 198 // Parse the authZ request, allow for OpenID 199 AuthorizationRequest authzRequest; 200 if (AuthenticationRequestDetector.isLikelyOpenID(params)) { 201 authzRequest = AuthenticationRequest.parse(params); 202 } else { 203 authzRequest = AuthorizationRequest.parse(params); 204 } 205 206 if (authzRequest.getRequestURI() != null) { 207 String msg = "Authorization request_uri parameter not allowed"; 208 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 209 } 210 211 if (clientAuth != null) { 212 return new PushedAuthorizationRequest(uri, clientAuth, authzRequest); 213 } else { 214 return new PushedAuthorizationRequest(uri, authzRequest); 215 } 216 } 217}