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}