001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk.device;
019
020
021import net.jcip.annotations.Immutable;
022
023import com.nimbusds.oauth2.sdk.id.Identifier;
024import com.nimbusds.oauth2.sdk.util.StringUtils;
025
026
027/**
028 * User code.
029 *
030 * <p>Related specifications:
031 *
032 * <ul>
033 *     <li>OAuth 2.0 Device Authorization Grant (RFC 8628)
034 * </ul>
035 */
036@Immutable
037public final class UserCode extends Identifier {
038        
039        
040        private static final long serialVersionUID = 6249537737406015901L;
041        
042        
043        public static final String LETTER_CHAR_SET = "BCDFGHJKLMNPQRSTVWXZ";
044        
045        
046        public static final String DIGIT_CHAR_SET = "0123456789";
047        
048        
049        /**
050         * The character set used by the identifier. The identifier can only
051         * contain characters from this set.
052         */
053        private final String charset;
054
055
056        /**
057         * Creates a new user code with the specified value.
058         *
059         * @param value   The code value. Must not be {@code null} or empty
060         *                string.
061         * @param charset The character set used by the identifier. The
062         *                identifier can only contain characters from this set.
063         *                If {@code null}, all characters are allowed.
064         */
065        public UserCode(final String value, final String charset) {
066
067                super(value);
068
069                this.charset = charset;
070        }
071
072
073        /**
074         * Creates a new user code with the specified value and the
075         * {@code LETTER_CHAR_SET}.
076         *
077         * @param value The code value. Must not be {@code null} or empty
078         *              string.
079         */
080        public UserCode(final String value) {
081
082                this(value, LETTER_CHAR_SET);
083        }
084
085
086        /**
087         * Creates a new user code with a randomly generated value with 8
088         * characters from {@code LETTER_CHAR_SET}, in the form
089         * {@code WDJB-MJHT}.
090         */
091        public UserCode() {
092
093                this(LETTER_CHAR_SET, 8);
094        }
095
096
097        /**
098         * Creates a new user code with a randomly generated value from the
099         * specified charset and length. A dash is added every 4 characters.
100         *
101         * @param charset The character set used by the identifier. The
102         *                identifier will contain characters from this set.
103         *                Must not be {@code null} or empty string.
104         * @param length  The length of the value to generate.
105         */
106        public UserCode(final String charset, final int length) {
107
108                this(generateValue(charset, length), charset);
109        }
110
111
112        /**
113         * Creates a new user code with a randomly generated value from the
114         * specified charset and length. A dash is added every 4 characters.
115         * 
116         * @param charset The character set used by the identifier. The
117         *                identifier will contain characters from this set.
118         *                Must not be {@code null} or empty string.
119         * @param length  The length of the value to generate.
120         */
121        private static String generateValue(final String charset, final int length) {
122
123                if (StringUtils.isBlank(charset))
124                        throw new IllegalArgumentException("The charset must not be null or empty string");
125
126                StringBuilder value = new StringBuilder();
127                for (int index = 0; index < length; index++) {
128                        if (index > 0 && index % 4 == 0)
129                                value.append('-');
130                        value.append(charset.charAt(secureRandom.nextInt(charset.length())));
131                }
132                return value.toString();
133        }
134
135
136        /**
137         * Returns the character set used by this {@code UserCode}.
138         * 
139         * @return The character set, or {@code null} if unspecified.
140         */
141        public String getCharset() {
142
143                return charset;
144        }
145
146
147        /**
148         * Returns the value with all invalid characters removed.
149         * 
150         * @return The value with all invalid characters removed.
151         */
152        public String getStrippedValue() {
153
154                return stripIllegalChars(getValue(), getCharset());
155        }
156
157
158        @Override
159        public int compareTo(final Identifier other) {
160
161                // fallback to default compare for other identifiers
162                if (!(other instanceof UserCode))
163                        return super.compareTo(other);
164
165                return getStrippedValue().compareTo(((UserCode) other).getStrippedValue());
166        }
167
168
169        @Override
170        public int hashCode() {
171
172                return getStrippedValue() != null ? getStrippedValue().hashCode() : 0;
173        }
174
175
176        @Override
177        public boolean equals(final Object object) {
178
179                return object instanceof UserCode
180                                && this.getStrippedValue().equals(((UserCode) object).getStrippedValue());
181        }
182
183
184        /**
185         * Removes all characters from {@code value} that are not in
186         * {@code charset}.
187         * 
188         * @param value   The code value.
189         * @param charset The allowed characters in {@code value}. If
190         *                {@code null} all characters are retained.
191         * @return The {@code value} with all invalid characters removed.
192         */
193        public static String stripIllegalChars(final String value, final String charset) {
194
195                if (charset == null)
196                        return value.toUpperCase();
197
198                StringBuilder newValue = new StringBuilder();
199                for (char curChar : value.toUpperCase().toCharArray()) {
200                        if (charset.indexOf(curChar) >= 0)
201                                newValue.append(curChar);
202                }
203                return newValue.toString();
204        }
205}