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        public static final String LETTER_CHAR_SET = "BCDFGHJKLMNPQRSTVWXZ";
041
042
043        public static final String DIGIT_CHAR_SET = "0123456789";
044
045
046        /**
047         * The character set used by the identifier. The identifier can only
048         * contain characters from this set.
049         */
050        private final String charset;
051
052
053        /**
054         * Creates a new user code with the specified value.
055         *
056         * @param value   The code value. Must not be {@code null} or empty
057         *                string.
058         * @param charset The character set used by the identifier. The
059         *                identifier can only contain characters from this set.
060         *                If {@code null}, all characters are allowed.
061         */
062        public UserCode(final String value, final String charset) {
063
064                super(value);
065
066                this.charset = charset;
067        }
068
069
070        /**
071         * Creates a new user code with the specified value and the
072         * {@code LETTER_CHAR_SET}.
073         *
074         * @param value The code value. Must not be {@code null} or empty
075         *              string.
076         */
077        public UserCode(final String value) {
078
079                this(value, LETTER_CHAR_SET);
080        }
081
082
083        /**
084         * Creates a new user code with a randomly generated value with 8
085         * characters from {@code LETTER_CHAR_SET}, in the form
086         * {@code WDJB-MJHT}.
087         */
088        public UserCode() {
089
090                this(LETTER_CHAR_SET, 8);
091        }
092
093
094        /**
095         * Creates a new user code with a randomly generated value from the
096         * specified charset and length. A dash is added every 4 characters.
097         */
098        public UserCode(final String charset, final int length) {
099
100                this(generateValue(charset, length), charset);
101        }
102
103
104        /**
105         * Creates a new user code with a randomly generated value from the
106         * specified charset and length. A dash is added every 4 characters.
107         * 
108         * @param charset The character set used by the identifier. The
109         *                identifier can only contain characters from this set.
110         *                Must not be {@code null} or empty string.
111         * @param length  The length of the value to generate.
112         */
113        private static String generateValue(final String charset, final int length) {
114
115                if (StringUtils.isBlank(charset))
116                        throw new IllegalArgumentException("The charset must not be null or empty string");
117
118                StringBuilder value = new StringBuilder();
119                for (int index = 0; index < length; index++) {
120                        if (index > 0 && index % 4 == 0)
121                                value.append('-');
122                        value.append(charset.charAt(secureRandom.nextInt(charset.length())));
123                }
124                return value.toString();
125        }
126
127
128        /**
129         * Returns the character set used by this {@code UserCode}.
130         * 
131         * @return The character set, or {@code null} if unspecified.
132         */
133        public String getCharset() {
134
135                return charset;
136        }
137
138
139        /**
140         * Returns the value with all invalid characters removed.
141         * 
142         * @return The value with all invalid characters removed.
143         */
144        public String getStrippedValue() {
145
146                return stripIllegalChars(getValue(), getCharset());
147        }
148
149
150        @Override
151        public int compareTo(final Identifier other) {
152
153                // fallback to default compare for other identifiers
154                if (!(other instanceof UserCode))
155                        return super.compareTo(other);
156
157                return getStrippedValue().compareTo(((UserCode) other).getStrippedValue());
158        }
159
160
161        @Override
162        public int hashCode() {
163
164                return getStrippedValue() != null ? getStrippedValue().hashCode() : 0;
165        }
166
167
168        @Override
169        public boolean equals(final Object object) {
170
171                return object instanceof UserCode
172                                && this.getStrippedValue().equals(((UserCode) object).getStrippedValue());
173        }
174
175
176        /**
177         * Removes all characters from {@code value} that are not in
178         * {@code charset}.
179         * 
180         * @param value   The code value.
181         * @param charset The allowed characters in {@code value}. If
182         *                {@code null} all characters are retained.
183         * @return The {@code value} with all invalid characters removed.
184         */
185        public static String stripIllegalChars(final String value, final String charset) {
186
187                if (charset == null)
188                        return value.toUpperCase();
189
190                StringBuilder newValue = new StringBuilder();
191                for (char curChar : value.toUpperCase().toCharArray()) {
192                        if (charset.indexOf(curChar) >= 0)
193                                newValue.append(curChar);
194                }
195                return newValue.toString();
196        }
197}