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