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