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>Example HTTP Authorization header (for client identifier "Aladdin"
086         * and password "open sesame"):
087         *
088         * <pre>
089         * Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
090         * </pre>
091         *
092         * <p>See RFC 2617, section 2.
093         *
094         * @return The HTTP Authorization header.
095         */
096        public String toHTTPAuthorizationHeader() {
097
098                StringBuilder sb = new StringBuilder();
099
100                try {
101                        sb.append(URLEncoder.encode(getClientID().getValue(), UTF8_CHARSET.name()));
102                        sb.append(':');
103                        sb.append(URLEncoder.encode(secret.getValue(), UTF8_CHARSET.name()));
104
105                } catch (UnsupportedEncodingException e) {
106
107                        // UTF-8 should always be supported
108                }
109
110                return "Basic " + Base64.encodeBase64String(sb.toString().getBytes(UTF8_CHARSET));
111        }
112        
113        
114        @Override
115        public void applyTo(final HTTPRequest httpRequest) {
116        
117                httpRequest.setAuthorization(toHTTPAuthorizationHeader());
118        }
119        
120        
121        /**
122         * Parses a client secret basic authentication from the specified HTTP
123         * Authorization header.
124         *
125         * @param header The HTTP Authorization header to parse. Must not be 
126         *               {@code null}.
127         *
128         * @return The client secret basic authentication.
129         *
130         * @throws ParseException If the header couldn't be parsed to a client
131         *                        secret basic authentication.
132         */
133        public static ClientSecretBasic parse(final String header)
134                throws ParseException {
135                
136                String[] parts = header.split("\\s");
137                
138                if (parts.length != 2)
139                        throw new ParseException("Unexpected number of HTTP Authorization header value parts: " + parts.length);
140                
141                if (! parts[0].equalsIgnoreCase("Basic"))
142                        throw new ParseException("HTTP authentication must be \"Basic\"");
143                
144                String credentialsString = new String(Base64.decodeBase64(parts[1]), UTF8_CHARSET);
145
146                String[] credentials = credentialsString.split(":", 2);
147                
148                if (credentials.length != 2)
149                        throw new ParseException("Missing credentials delimiter \":\"");
150
151                try {
152                        String decodedClientID = URLDecoder.decode(credentials[0], UTF8_CHARSET.name());
153                        String decodedSecret = URLDecoder.decode(credentials[1], UTF8_CHARSET.name());
154
155                        return new ClientSecretBasic(new ClientID(decodedClientID), new Secret(decodedSecret));
156                        
157                } catch (UnsupportedEncodingException e) {
158                
159                        throw new ParseException(e.getMessage(), e);
160                }
161        }
162        
163        
164        /**
165         * Parses a client secret basic authentication from the specified HTTP
166         * request.
167         *
168         * @param httpRequest The HTTP request to parse. Must not be 
169         *                    {@code null} and must contain a valid 
170         *                    Authorization header.
171         *
172         * @return The client secret basic authentication.
173         *
174         * @throws ParseException If the HTTP Authorization header couldn't be 
175         *                        parsed to a client secret basic 
176         *                        authentication.
177         */
178        public static ClientSecretBasic parse(final HTTPRequest httpRequest)
179                throws ParseException {
180                
181                String header = httpRequest.getAuthorization();
182                
183                if (header == null)
184                        throw new ParseException("Missing HTTP Authorization header");
185                        
186                return parse(header);
187        }
188}