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}