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        public UserCode(final String charset, final int length) {
102
103                this(generateValue(charset, length), charset);
104        }
105
106
107        /**
108         * Creates a new user code with a randomly generated value from the
109         * specified charset and length. A dash is added every 4 characters.
110         * 
111         * @param charset The character set used by the identifier. The
112         *                identifier can only contain characters from this set.
113         *                Must not be {@code null} or empty string.
114         * @param length  The length of the value to generate.
115         */
116        private static String generateValue(final String charset, final int length) {
117
118                if (StringUtils.isBlank(charset))
119                        throw new IllegalArgumentException("The charset must not be null or empty string");
120
121                StringBuilder value = new StringBuilder();
122                for (int index = 0; index < length; index++) {
123                        if (index > 0 && index % 4 == 0)
124                                value.append('-');
125                        value.append(charset.charAt(secureRandom.nextInt(charset.length())));
126                }
127                return value.toString();
128        }
129
130
131        /**
132         * Returns the character set used by this {@code UserCode}.
133         * 
134         * @return The character set, or {@code null} if unspecified.
135         */
136        public String getCharset() {
137
138                return charset;
139        }
140
141
142        /**
143         * Returns the value with all invalid characters removed.
144         * 
145         * @return The value with all invalid characters removed.
146         */
147        public String getStrippedValue() {
148
149                return stripIllegalChars(getValue(), getCharset());
150        }
151
152
153        @Override
154        public int compareTo(final Identifier other) {
155
156                // fallback to default compare for other identifiers
157                if (!(other instanceof UserCode))
158                        return super.compareTo(other);
159
160                return getStrippedValue().compareTo(((UserCode) other).getStrippedValue());
161        }
162
163
164        @Override
165        public int hashCode() {
166
167                return getStrippedValue() != null ? getStrippedValue().hashCode() : 0;
168        }
169
170
171        @Override
172        public boolean equals(final Object object) {
173
174                return object instanceof UserCode
175                                && this.getStrippedValue().equals(((UserCode) object).getStrippedValue());
176        }
177
178
179        /**
180         * Removes all characters from {@code value} that are not in
181         * {@code charset}.
182         * 
183         * @param value   The code value.
184         * @param charset The allowed characters in {@code value}. If
185         *                {@code null} all characters are retained.
186         * @return The {@code value} with all invalid characters removed.
187         */
188        public static String stripIllegalChars(final String value, final String charset) {
189
190                if (charset == null)
191                        return value.toUpperCase();
192
193                StringBuilder newValue = new StringBuilder();
194                for (char curChar : value.toUpperCase().toCharArray()) {
195                        if (charset.indexOf(curChar) >= 0)
196                                newValue.append(curChar);
197                }
198                return newValue.toString();
199        }
200}