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