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.net.URISyntaxException; 023import java.util.Collections; 024import java.util.List; 025import java.util.Map; 026 027import net.jcip.annotations.Immutable; 028 029import com.nimbusds.common.contenttype.ContentType; 030import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; 031import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; 032import com.nimbusds.oauth2.sdk.http.HTTPRequest; 033import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 034import com.nimbusds.oauth2.sdk.util.StringUtils; 035import com.nimbusds.oauth2.sdk.util.URLUtils; 036import com.nimbusds.openid.connect.sdk.AuthenticationRequest; 037import com.nimbusds.openid.connect.sdk.op.AuthenticationRequestDetector; 038 039 040/** 041 * Pushed authorisation request. 042 * 043 * <p>Example HTTP request: 044 * 045 * <pre> 046 * POST /as/par HTTP/1.1 047 * Host: as.example.com 048 * Content-Type: application/x-www-form-urlencoded 049 * Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3 050 * 051 * response_type=code 052 * &client_id=s6BhdRkqt3&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 (draft-ietf-oauth-par-02) 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; 172 try { 173 uri = httpRequest.getURL().toURI(); 174 } catch (URISyntaxException e) { 175 throw new ParseException(e.getMessage(), e); 176 } 177 178 httpRequest.ensureMethod(HTTPRequest.Method.POST); 179 httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED); 180 181 // Parse client authentication, if any 182 ClientAuthentication clientAuth; 183 try { 184 clientAuth = ClientAuthentication.parse(httpRequest); 185 } catch (ParseException e) { 186 throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage())); 187 } 188 189 // No fragment! May use query component! 190 Map<String,List<String>> params = httpRequest.getQueryParameters(); 191 192 // Multiple conflicting client auth methods (issue #203)? 193 if (clientAuth instanceof ClientSecretBasic) { 194 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) || StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) { 195 String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion"; 196 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 197 } 198 } 199 200 // client_id not required in authZ params if auth is present 201 if (! params.containsKey("client_id") && clientAuth != null) { 202 params.put("client_id", Collections.singletonList(clientAuth.getClientID().getValue())); 203 } 204 205 // Parse the authZ request, allow for OpenID 206 AuthorizationRequest authzRequest; 207 if (AuthenticationRequestDetector.isLikelyOpenID(params)) { 208 authzRequest = AuthenticationRequest.parse(params); 209 } else { 210 authzRequest = AuthorizationRequest.parse(params); 211 } 212 213 if (authzRequest.getRequestURI() != null) { 214 String msg = "Authorization request_uri parameter not allowed"; 215 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 216 } 217 218 if (clientAuth != null) { 219 return new PushedAuthorizationRequest(uri, clientAuth, authzRequest); 220 } else { 221 return new PushedAuthorizationRequest(uri, authzRequest); 222 } 223 } 224}