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