diff --git a/.jhipster/Token.json b/.jhipster/Token.json index f1b13b58a..f95832f3f 100644 --- a/.jhipster/Token.json +++ b/.jhipster/Token.json @@ -36,6 +36,10 @@ "fieldName": "renewable", "fieldType": "Boolean", "fieldValidateRules": ["required"] + }, + { + "fieldName": "newToken", + "fieldType": "String" } ], "changelogDate": "20190823204705", diff --git a/jhipster-jdl.jdl b/jhipster-jdl.jdl index d33ddd22d..f1947c306 100644 --- a/jhipster-jdl.jdl +++ b/jhipster-jdl.jdl @@ -4,7 +4,8 @@ entity Token { expiration Instant, usageLimit Integer, currentUsage Integer required, - renewable Boolean required + renewable Boolean required, + newToken String } entity TokenStats { diff --git a/src/main/java/org/mskcc/cbio/oncokb/domain/Token.java b/src/main/java/org/mskcc/cbio/oncokb/domain/Token.java index ad58f95aa..009f19b9a 100644 --- a/src/main/java/org/mskcc/cbio/oncokb/domain/Token.java +++ b/src/main/java/org/mskcc/cbio/oncokb/domain/Token.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.hibernate.annotations.Type; +import org.mskcc.cbio.oncokb.domain.enumeration.TokenType; import javax.persistence.*; import javax.validation.constraints.*; @@ -44,6 +45,10 @@ public class Token implements Serializable { @Column(name = "renewable", nullable = false) private Boolean renewable = true; + @Convert(converter = TokenKeyConverter.class) + @Column(name = "new_token") + private TokenKey newToken; + @ManyToOne @JsonIgnoreProperties(value = "tokens", allowSetters = true) private User user; @@ -135,6 +140,19 @@ public void setRenewable(Boolean renewable) { this.renewable = renewable; } + public TokenKey getNewToken() { + return newToken; + } + + public Token newToken(TokenKey newToken) { + this.newToken = newToken; + return this; + } + + public void setNewToken(TokenKey newToken) { + this.newToken = newToken; + } + public User getUser() { return user; } @@ -176,6 +194,54 @@ public String toString() { ", usageLimit=" + getUsageLimit() + ", currentUsage=" + getCurrentUsage() + ", renewable='" + isRenewable() + "'" + + ", newToken='" + getNewToken() + "'" + "}"; } } + +class TokenKeyConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(TokenKey tokenKey) { + if (tokenKey.getTokenType() == null || tokenKey.getToken() == null || tokenKey.getChecksum() == null) { + return null; + } + if (tokenKey.getToken().length() != TokenKey.TOKEN_CHAR_LENGTH || tokenKey.getChecksum().length() != TokenKey.CHECKSUM_CHAR_LENGTH) { + return null; + } + return tokenKey.getTokenType().getType() + "_" + tokenKey.getToken() + tokenKey.getChecksum(); + } + + @Override + public TokenKey convertToEntityAttribute(String dbData) { + if (dbData == null) { + return null; + } + String[] parts = dbData.split("_"); + if (parts.length != 2) { + return null; + } + + + TokenKey tokenKey = new TokenKey(); + String type = parts[0]; + if (type.equals(TokenType.SERVICE.getType())) { + tokenKey.setTokenType(TokenType.SERVICE); + } else if (type.equals(TokenType.USER.getType())) { + tokenKey.setTokenType(TokenType.USER); + } else { + return null; + } + + String tokenAndChecksum = parts[1]; + if (tokenAndChecksum.length() == TokenKey.TOKEN_CHAR_LENGTH + TokenKey.CHECKSUM_CHAR_LENGTH) { + String token = tokenAndChecksum.substring(0, TokenKey.TOKEN_CHAR_LENGTH); + String checksum = tokenAndChecksum.substring(TokenKey.TOKEN_CHAR_LENGTH); + tokenKey.setToken(token); + tokenKey.setChecksum(checksum); + } else { + return null; + } + + return tokenKey; + } +} \ No newline at end of file diff --git a/src/main/java/org/mskcc/cbio/oncokb/domain/TokenKey.java b/src/main/java/org/mskcc/cbio/oncokb/domain/TokenKey.java new file mode 100644 index 000000000..904df66e5 --- /dev/null +++ b/src/main/java/org/mskcc/cbio/oncokb/domain/TokenKey.java @@ -0,0 +1,84 @@ +package org.mskcc.cbio.oncokb.domain; + +import java.io.Serializable; +import java.security.SecureRandom; +import java.util.zip.CRC32; + +import org.apache.commons.lang3.StringUtils; +import org.mskcc.cbio.oncokb.domain.enumeration.TokenType; + +public class TokenKey implements Serializable { + public static int TOKEN_CHAR_LENGTH = 30; + + public static int CHECKSUM_CHAR_LENGTH = 6; + + private static String BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + private TokenType tokenType; + + private String token; + + private String checksum; + + public static TokenKey generate(TokenType type) { + TokenKey tokenKey = new TokenKey(); + tokenKey.setTokenType(type); + + CRC32 crc32 = new CRC32(); + + String token = generateToken(); + tokenKey.setToken(token); + + crc32.update(token.getBytes()); + String base62Checksum = toBase62(crc32.getValue()); + if (base62Checksum.length() < CHECKSUM_CHAR_LENGTH) { + base62Checksum = StringUtils.repeat('0', CHECKSUM_CHAR_LENGTH - base62Checksum.length()); + } + tokenKey.setChecksum(base62Checksum); + + return tokenKey; + } + + private static String generateToken() { + SecureRandom secureRandom = new SecureRandom(); + StringBuilder token = new StringBuilder(); + for (int i = 0; i < TOKEN_CHAR_LENGTH; i++) { + token.append(BASE62_CHARS.charAt(secureRandom.nextInt(BASE62_CHARS.length()))); + } + return token.toString(); + } + + public static String toBase62(long val) { + StringBuffer sb = new StringBuffer(); + while(val > 0) { + int remainder = (int) (val % 62); + val = val / 62; + sb.insert(0, BASE62_CHARS.charAt((int) remainder)); + } + return sb.toString(); + } + + public TokenType getTokenType() { + return tokenType; + } + + public void setTokenType(TokenType tokenType) { + this.tokenType = tokenType; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getChecksum() { + return checksum; + } + + public void setChecksum(String checksum) { + this.checksum = checksum; + } +} diff --git a/src/main/java/org/mskcc/cbio/oncokb/domain/enumeration/TokenType.java b/src/main/java/org/mskcc/cbio/oncokb/domain/enumeration/TokenType.java new file mode 100644 index 000000000..b54ae5dcf --- /dev/null +++ b/src/main/java/org/mskcc/cbio/oncokb/domain/enumeration/TokenType.java @@ -0,0 +1,16 @@ +package org.mskcc.cbio.oncokb.domain.enumeration; + +public enum TokenType { + USER("ocku"), + SERVICE("okbs"); + + String type; + + TokenType(String type) { + this.type = type; + } + + public String getType() { + return this.type; + } +} diff --git a/src/main/java/org/mskcc/cbio/oncokb/security/uuid/TokenProvider.java b/src/main/java/org/mskcc/cbio/oncokb/security/uuid/TokenProvider.java index 08ce324aa..b01c90c22 100644 --- a/src/main/java/org/mskcc/cbio/oncokb/security/uuid/TokenProvider.java +++ b/src/main/java/org/mskcc/cbio/oncokb/security/uuid/TokenProvider.java @@ -2,7 +2,9 @@ import org.mskcc.cbio.oncokb.domain.Authority; import org.mskcc.cbio.oncokb.domain.Token; +import org.mskcc.cbio.oncokb.domain.TokenKey; import org.mskcc.cbio.oncokb.domain.User; +import org.mskcc.cbio.oncokb.domain.enumeration.TokenType; import org.mskcc.cbio.oncokb.repository.UserRepository; import org.mskcc.cbio.oncokb.security.AuthoritiesConstants; import org.mskcc.cbio.oncokb.security.SecurityUtils; @@ -74,6 +76,7 @@ private Token getNewToken(Set authorities, Optional definedE token.setExpiration(expirationTime); } token.setToken(UUID.randomUUID()); + token.setNewToken(TokenKey.generate(TokenType.USER)); return token; } diff --git a/src/main/resources/config/liquibase/changelog/20241118184124_added_field_newToken_to_Token.xml b/src/main/resources/config/liquibase/changelog/20241118184124_added_field_newToken_to_Token.xml new file mode 100644 index 000000000..dac25f73e --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20241118184124_added_field_newToken_to_Token.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index b90a7fbb7..68ea5b5a5 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -27,6 +27,7 @@ + diff --git a/src/test/java/org/mskcc/cbio/oncokb/domain/TokenKeyTest.java b/src/test/java/org/mskcc/cbio/oncokb/domain/TokenKeyTest.java new file mode 100644 index 000000000..c4a25a1da --- /dev/null +++ b/src/test/java/org/mskcc/cbio/oncokb/domain/TokenKeyTest.java @@ -0,0 +1,31 @@ +package org.mskcc.cbio.oncokb.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.ByteBuffer; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; + +public class TokenKeyTest { + @Test + public void toBase62() { + String test = "d"; + assertThat(TokenKey.toBase62(stringToLong(test))).isEqualTo("1c"); + + test = "hello"; + assertThat(TokenKey.toBase62(stringToLong(test))).isEqualTo("7tQLFHz"); + + test = "oncokb"; + assertThat(TokenKey.toBase62(122519905332066L)).isEqualTo("Yn1xclvu"); + } + + private long stringToLong(String str) { + if (str.length() < Long.BYTES) { + str = StringUtils.repeat('\0', Long.BYTES - str.length()) + str; + } + + ByteBuffer buffer = ByteBuffer.wrap(str.getBytes()); + return buffer.getLong(0); + } +}