001    package com.nimbusds.oauth2.sdk.auth;
002    
003    
004    import java.io.UnsupportedEncodingException;
005    
006    import java.net.URLDecoder;
007    import java.net.URLEncoder;
008    
009    import net.jcip.annotations.Immutable;
010    
011    import org.apache.commons.codec.binary.Base64;
012    
013    import com.nimbusds.oauth2.sdk.ParseException;
014    
015    import com.nimbusds.oauth2.sdk.id.ClientID;
016    
017    import com.nimbusds.oauth2.sdk.http.HTTPRequest;
018    
019    
020    /**
021     * Client secret basic authentication at the Token endpoint. Implements
022     * {@link ClientAuthenticationMethod#CLIENT_SECRET_BASIC}. This class is
023     * immutable.
024     *
025     * <p>Example HTTP Authorization header (for client identifier "s6BhdRkqt3" and
026     * secret "7Fjfp0ZBr1KtDRbnfVdmIw"):
027     *
028     * <pre>
029     * Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3
030     * </pre>
031     *
032     * <p>Related specifications:
033     *
034     * <ul>
035     *     <li>OAuth 2.0 (RFC 6749), section 2.3.1.
036     *     <li>HTTP Authentication: Basic and Digest Access Authentication 
037     *         (RFC 2617).
038     * </ul>
039     *
040     * @author Vladimir Dzhuvinov
041     * @version $version$ (2013-05-13)
042     */
043    @Immutable
044    public final class ClientSecretBasic extends ClientAuthentication {
045    
046    
047            /**
048             * The client ID.
049             */
050            private final ClientID clientID;
051            
052            
053            /**
054             * The client secret.
055             */
056            private final Secret secret;
057            
058            
059            /**
060             * Creates a new client secret basic authentication.
061             *
062             * @param clientID The client identifier. Must not be {@code null}.
063             * @param secret   The client secret. Must not be {@code null}.
064             */
065            public ClientSecretBasic(final ClientID clientID, final Secret secret) {
066            
067                    super(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
068            
069                    if (clientID == null)
070                            throw new IllegalArgumentException("The client ID must not be null");
071                    
072                    this.clientID = clientID;
073                    
074                    if (secret == null)
075                            throw new IllegalArgumentException("The client secret must not be null");
076                    
077                    this.secret = secret;
078            }
079            
080            
081            /**
082             * Gets the client identifier.
083             *
084             * @return The client identifier.
085             */
086            public ClientID getClientID() {
087            
088                    return clientID;
089            }
090            
091            
092            /**
093             * Gets the client secret.
094             *
095             * @return The client secret.
096             */
097            public Secret getClientSecret() {
098            
099                    return secret;
100            }
101            
102            
103            /**
104             * Returns the HTTP Authorization header representation of this client
105             * secret basic authentication.
106             *
107             * <p>Example HTTP Authorization header (for client identifier "Aladdin"
108             * and password "open sesame"):
109             *
110             * <pre>
111             * Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
112             * </pre>
113             *
114             * <p>See RFC 2617, section 2.
115             *
116             * @return The HTTP Authorization header.
117             */
118            public String toHTTPAuthorizationHeader() {
119                    
120                    String b64 = null;
121                    
122                    try {
123                            String encodedClientID = URLEncoder.encode(clientID.toString(), "UTF-8");
124                            String encodedSecret = URLEncoder.encode(secret.getValue(), "UTF-8");
125    
126                            StringBuilder sb = new StringBuilder(encodedClientID);
127                            sb.append(':');
128                            sb.append(encodedSecret);
129    
130                            b64 = Base64.encodeBase64String(sb.toString().getBytes("UTF-8"));
131                            
132                    } catch (UnsupportedEncodingException e) {
133                    
134                            // UTF-8 should always be supported
135                    }
136                    
137                    return "Basic " + b64;
138            }
139            
140            
141            @Override
142            public void applyTo(final HTTPRequest httpRequest) {
143            
144                    httpRequest.setAuthorization(toHTTPAuthorizationHeader());
145            }
146            
147            
148            /**
149             * Parses a client secret basic authentication from the specified HTTP
150             * Authorization header.
151             *
152             * @param header The HTTP Authorization header to parse. Must not be 
153             *               {@code null}.
154             *
155             * @return The client secret basic authentication.
156             *
157             * @throws ParseException If the header couldn't be parsed to a client
158             *                        secret basic authentication.
159             */
160            public static ClientSecretBasic parse(final String header)
161                    throws ParseException {
162                    
163                    String[] parts = header.split("\\s");
164                    
165                    if (parts.length != 2)
166                            throw new ParseException("Unexpected number of HTTP Authorization header value parts: " + parts.length);
167                    
168                    if (! parts[0].equalsIgnoreCase("Basic"))
169                            throw new ParseException("HTTP authentication must be \"Basic\"");
170                    
171                    try {
172                            String credentialsString = new String(Base64.decodeBase64(parts[1]), "utf-8");
173    
174                            String[] credentials = credentialsString.split(":", 2);
175                    
176                            if (credentials.length != 2)
177                                    throw new ParseException("Missing credentials delimiter \":\"");
178    
179                            String decodedClientID = URLDecoder.decode(credentials[0], "utf-8");
180                            String decodedSecret = URLDecoder.decode(credentials[1], "utf-8");
181    
182                            return new ClientSecretBasic(new ClientID(decodedClientID), new Secret(decodedSecret));
183                            
184                    } catch (UnsupportedEncodingException e) {
185                    
186                            throw new ParseException(e.getMessage(), e);
187                    }
188            }
189            
190            
191            /**
192             * Parses a client secret basic authentication from the specified HTTP
193             * request.
194             *
195             * @param httpRequest The HTTP request to parse. Must not be 
196             *                    {@code null} and must contain a valid 
197             *                    Authorization header.
198             *
199             * @return The client secret basic authentication.
200             *
201             * @throws ParseException If the HTTP Authorization header couldn't be 
202             *                        parsed to a client secret basic 
203             *                        authentication.
204             */
205            public static ClientSecretBasic parse(final HTTPRequest httpRequest)
206                    throws ParseException {
207                    
208                    String header = httpRequest.getAuthorization();
209                    
210                    if (header == null)
211                            throw new ParseException("Missing HTTP Authorization header");
212                            
213                    return parse(header);
214            }
215    }