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 052 * &state=af0ifjsldkj 053 * &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb 054 * </pre> 055 * 056 * <p>Related specifications: 057 * 058 * <ul> 059 * <li>OAuth 2.0 Pushed Authorization Requests (RFC 9126) 060 * </ul> 061 */ 062@Immutable 063public class PushedAuthorizationRequest extends AbstractOptionallyAuthenticatedRequest { 064 065 066 /** 067 * The pushed authorisation request. 068 */ 069 private final AuthorizationRequest authzRequest; 070 071 072 /** 073 * Creates a new authenticated pushed authorisation request for a 074 * confidential client. 075 * 076 * @param uri The URI of the token endpoint. May be 077 * {@code null} if the {@link #toHTTPRequest} 078 * method will not be used. 079 * @param clientAuth The client authentication. Must not be 080 * {@code null}. 081 * @param authzRequest The authorisation request. Must not be 082 * {@code null}. 083 */ 084 public PushedAuthorizationRequest(final URI uri, 085 final ClientAuthentication clientAuth, 086 final AuthorizationRequest authzRequest) { 087 super(uri, clientAuth); 088 089 if (clientAuth == null) 090 throw new IllegalArgumentException("The client authentication must not be null"); 091 092 if (authzRequest == null) { 093 throw new IllegalArgumentException("The authorization request must not be null"); 094 } 095 if (authzRequest.getRequestURI() != null) { 096 throw new IllegalArgumentException("Authorization request_uri parameter not allowed"); 097 } 098 this.authzRequest = authzRequest; 099 } 100 101 102 /** 103 * Creates a new pushed authorisation request for a public client. 104 * 105 * @param uri The URI of the token endpoint. May be 106 * {@code null} if the {@link #toHTTPRequest} 107 * method will not be used. 108 * @param authzRequest The authorisation request. Must not be 109 * {@code null}. 110 */ 111 public PushedAuthorizationRequest(final URI uri, 112 final AuthorizationRequest authzRequest) { 113 114 super(uri, null); 115 if (authzRequest == null) { 116 throw new IllegalArgumentException("The authorization request must not be null"); 117 } 118 if (authzRequest.getRequestURI() != null) { 119 throw new IllegalArgumentException("Authorization request_uri parameter not allowed"); 120 } 121 this.authzRequest = authzRequest; 122 } 123 124 125 /** 126 * Returns the pushed authorisation request. 127 * 128 * @return The pushed authorisation request. 129 */ 130 public AuthorizationRequest getAuthorizationRequest() { 131 return authzRequest; 132 } 133 134 135 @Override 136 public HTTPRequest toHTTPRequest() { 137 138 if (getEndpointURI() == null) 139 throw new SerializeException("The endpoint URI is not specified"); 140 141 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI()); 142 httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED); 143 144 if (getClientAuthentication() != null) { 145 getClientAuthentication().applyTo(httpRequest); 146 } 147 148 Map<String, List<String>> params = httpRequest.getQueryParameters(); 149 params.putAll(authzRequest.toParameters()); 150 httpRequest.setQuery(URLUtils.serializeParameters(params)); 151 152 return httpRequest; 153 } 154 155 156 /** 157 * Parses a pushed authorisation request from the specified HTTP 158 * request. 159 * 160 * @param httpRequest The HTTP request. Must not be {@code null}. 161 * 162 * @return The pushed authorisation request. 163 * 164 * @throws ParseException If the HTTP request couldn't be parsed to a 165 * pushed authorisation request. 166 */ 167 public static PushedAuthorizationRequest parse(final HTTPRequest httpRequest) 168 throws ParseException { 169 170 // Only HTTP POST accepted 171 URI uri = httpRequest.getURI(); 172 httpRequest.ensureMethod(HTTPRequest.Method.POST); 173 httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED); 174 175 // Parse client authentication, if any 176 ClientAuthentication clientAuth; 177 try { 178 clientAuth = ClientAuthentication.parse(httpRequest); 179 } catch (ParseException e) { 180 throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage())); 181 } 182 183 // No fragment! May use query component! 184 Map<String,List<String>> params = httpRequest.getQueryParameters(); 185 186 // Multiple conflicting client auth methods (issue #203)? 187 if (clientAuth instanceof ClientSecretBasic) { 188 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) || StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) { 189 String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion"; 190 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 191 } 192 } 193 194 // client_id not required in authZ params if auth is present 195 if (! params.containsKey("client_id") && clientAuth != null) { 196 params.put("client_id", Collections.singletonList(clientAuth.getClientID().getValue())); 197 } 198 199 // Parse the authZ request, allow for OpenID 200 AuthorizationRequest authzRequest; 201 if (AuthenticationRequestDetector.isLikelyOpenID(params)) { 202 authzRequest = AuthenticationRequest.parse(params); 203 } else { 204 authzRequest = AuthorizationRequest.parse(params); 205 } 206 207 if (authzRequest.getRequestURI() != null) { 208 String msg = "Authorization request_uri parameter not allowed"; 209 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 210 } 211 212 if (clientAuth != null) { 213 return new PushedAuthorizationRequest(uri, clientAuth, authzRequest); 214 } else { 215 return new PushedAuthorizationRequest(uri, authzRequest); 216 } 217 } 218}