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