001package com.nimbusds.oauth2.sdk.auth;
002
003
004import java.io.UnsupportedEncodingException;
005import java.net.URLDecoder;
006import java.net.URLEncoder;
007
008import net.jcip.annotations.Immutable;
009
010import org.apache.commons.codec.binary.Base64;
011
012import com.nimbusds.oauth2.sdk.ParseException;
013import com.nimbusds.oauth2.sdk.id.ClientID;
014import 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
040public 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}