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 com.nimbusds.common.contenttype.ContentType; 022import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; 023import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; 024import com.nimbusds.oauth2.sdk.http.HTTPRequest; 025import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 026import com.nimbusds.oauth2.sdk.util.StringUtils; 027import com.nimbusds.oauth2.sdk.util.URLUtils; 028import com.nimbusds.openid.connect.sdk.AuthenticationRequest; 029import com.nimbusds.openid.connect.sdk.op.AuthenticationRequestDetector; 030import net.jcip.annotations.Immutable; 031 032import java.net.URI; 033import java.util.Collections; 034import java.util.LinkedHashMap; 035import java.util.List; 036import java.util.Map; 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; 149 try { 150 params = new LinkedHashMap<>(httpRequest.getBodyAsFormParameters()); 151 } catch (ParseException e) { 152 throw new SerializeException(e.getMessage(), e); 153 } 154 params.putAll(authzRequest.toParameters()); 155 httpRequest.setBody(URLUtils.serializeParameters(params)); 156 157 return httpRequest; 158 } 159 160 161 /** 162 * Parses a pushed authorisation request from the specified HTTP 163 * request. 164 * 165 * @param httpRequest The HTTP request. Must not be {@code null}. 166 * 167 * @return The pushed authorisation request. 168 * 169 * @throws ParseException If the HTTP request couldn't be parsed to a 170 * pushed authorisation request. 171 */ 172 public static PushedAuthorizationRequest parse(final HTTPRequest httpRequest) 173 throws ParseException { 174 175 // Only HTTP POST accepted 176 URI uri = httpRequest.getURI(); 177 httpRequest.ensureMethod(HTTPRequest.Method.POST); 178 httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED); 179 180 // Parse client authentication, if any 181 ClientAuthentication clientAuth; 182 try { 183 clientAuth = ClientAuthentication.parse(httpRequest); 184 } catch (ParseException e) { 185 throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage())); 186 } 187 188 // No fragment! May use query component! 189 Map<String,List<String>> params = httpRequest.getBodyAsFormParameters(); 190 191 // Multiple conflicting client auth methods (issue #203)? 192 if (clientAuth instanceof ClientSecretBasic) { 193 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) || StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) { 194 String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion"; 195 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 196 } 197 } 198 199 // client_id not required in authZ params if auth is present 200 if (! params.containsKey("client_id") && clientAuth != null) { 201 params.put("client_id", Collections.singletonList(clientAuth.getClientID().getValue())); 202 } 203 204 // Parse the authZ request, allow for OpenID 205 AuthorizationRequest authzRequest; 206 if (AuthenticationRequestDetector.isLikelyOpenID(params)) { 207 authzRequest = AuthenticationRequest.parse(params); 208 } else { 209 authzRequest = AuthorizationRequest.parse(params); 210 } 211 212 if (authzRequest.getRequestURI() != null) { 213 String msg = "Authorization request_uri parameter not allowed"; 214 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 215 } 216 217 if (clientAuth != null) { 218 return new PushedAuthorizationRequest(uri, clientAuth, authzRequest); 219 } else { 220 return new PushedAuthorizationRequest(uri, authzRequest); 221 } 222 } 223}