Skip to content

Commit 173fcf5

Browse files
authored
Issue 48298: Align API Filter Operators (#60)
1 parent 37957ca commit 173fcf5

12 files changed

+203
-11
lines changed

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# The LabKey Remote API Library for Java - Change Log
22

3-
## version 5.3.0-SNAPSHOT
3+
## version 5.4.0-SNAPSHOT
44
*Released*: TBD
5-
* Update to Gradle 8.2.1 and adjust away from deprecated syntax
5+
6+
## version 5.3.0
7+
*Released*: 7 September 2023
8+
* Update to Gradle 8.3 and adjust away from deprecated syntax
9+
* Add Ontology and Lineage filter type operators
10+
* Add `TotpManager`, a simple helper that generates TOTP one-time passwords
11+
* Update Commons Codec, HttpCore, and Gradle Plugins versions
612

713
## version 5.2.0
814
*Released*: 3 May 2023

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ repositories {
7171

7272
group "org.labkey.api"
7373

74-
version "5.3.0-SNAPSHOT"
74+
version "5.4.0-SNAPSHOT"
7575

7676
dependencies {
7777
api "org.json:json:${jsonObjectVersion}"

gradle.properties

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ artifactory_contextUrl=https://labkey.jfrog.io/artifactory
77
sourceCompatibility=17
88
targetCompatibility=17
99

10-
gradlePluginsVersion=1.41.0
10+
gradlePluginsVersion=1.41.2
1111

12-
commonsCodecVersion=1.15
12+
commonsCodecVersion=1.16.0
1313
commonsLoggingVersion=1.2
1414

1515
hamcrestVersion=1.3
1616

1717
httpclient5Version=5.2.1
18-
httpcore5Version=5.2.1
18+
httpcore5Version=5.2.2
1919

2020
jsonObjectVersion=20230618
2121

gradle/wrapper/gradle-wrapper.jar

1.27 KB
Binary file not shown.
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
44
networkTimeout=10000
5+
validateDistributionUrl=true
56
zipStoreBase=GRADLE_USER_HOME
67
zipStorePath=wrapper/dists

gradlew

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,13 @@ location of your Java installation."
130130
fi
131131
else
132132
JAVACMD=java
133-
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
133+
if ! command -v java >/dev/null 2>&1
134+
then
135+
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
134136
135137
Please set the JAVA_HOME variable in your environment to match the
136138
location of your Java installation."
139+
fi
137140
fi
138141

139142
# Increase the maximum file descriptors if we can.

src/org/labkey/remoteapi/query/Filter.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ public enum Operator
3030
// WARNING: Keep in sync and in order with all other client apis and docs:
3131
// - server: CompareType.java
3232
// - java: Filter.java
33-
// - js: Filter.js
33+
// - js: Types.ts
3434
// - R: makeFilter.R, makeFilter.Rd
35-
// - Python & Perl don't have a filter operator enum
35+
// - Python: query.py
36+
// - Perl doesn't have a filter operator enum
3637

3738
//
3839
// These operators require a data value
@@ -90,7 +91,22 @@ public enum Operator
9091
//
9192

9293
Q("Search", "q", "Q", true),
93-
WHERE("Where", "where", "WHERE", true)
94+
WHERE("Where", "where", "WHERE", true),
95+
96+
//
97+
// Ontology operators
98+
//
99+
100+
ONTOLOGY_IN_SUBTREE("Is In Subtree", "concept:insubtree", "ONTOLOGY_IN_SUBTREE", true),
101+
ONTOLOGY_NOT_IN_SUBTREE("Is Not In Subtree", "concept:notinsubtree", "ONTOLOGY_NOT_IN_SUBTREE", true),
102+
103+
//
104+
// Lineage operators
105+
//
106+
107+
EXP_CHILD_OF("Is Child Of", "exp:childof", "EXP_CHILD_OF", true),
108+
EXP_PARENT_OF("Is Parent Of", "exp:parentof", "EXP_PARENT_OF", true),
109+
EXP_LINEAGE_OF("In The Lineage Of", "exp:lineageof", "EXP_LINEAGE_OF", true)
94110
;
95111

96112
private static final Map<String, Operator> _programmaticNameToOperator = Arrays.stream(Operator.values())
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright (c) 2019 Sam Stevens
3+
*
4+
* Licensed under the MIT License
5+
*
6+
* https://github.com/samdjstevens v1.7.1
7+
*/
8+
package org.labkey.remoteapi.totp;
9+
10+
public class CodeGenerationException extends Exception {
11+
public CodeGenerationException(String message, Throwable cause) {
12+
super(message, cause);
13+
}
14+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2019 Sam Stevens
3+
*
4+
* Licensed under the MIT License
5+
*
6+
* https://github.com/samdjstevens v1.7.1
7+
*/
8+
package org.labkey.remoteapi.totp;
9+
10+
import org.apache.commons.codec.binary.Base32;
11+
12+
import javax.crypto.Mac;
13+
import javax.crypto.spec.SecretKeySpec;
14+
import java.security.InvalidKeyException;
15+
import java.security.InvalidParameterException;
16+
import java.security.NoSuchAlgorithmException;
17+
18+
public class CodeGenerator
19+
{
20+
private final HashingAlgorithm algorithm;
21+
private final int digits;
22+
23+
public CodeGenerator(HashingAlgorithm algorithm, int digits) {
24+
if (algorithm == null) {
25+
throw new InvalidParameterException("HashingAlgorithm must not be null.");
26+
}
27+
if (digits < 1) {
28+
throw new InvalidParameterException("Number of digits must be higher than 0.");
29+
}
30+
31+
this.algorithm = algorithm;
32+
this.digits = digits;
33+
}
34+
35+
public String generate(String key, long counter) throws CodeGenerationException {
36+
try {
37+
byte[] hash = generateHash(key, counter);
38+
return getDigitsFromHash(hash);
39+
} catch (Exception e) {
40+
throw new CodeGenerationException("Failed to generate code. See nested exception.", e);
41+
}
42+
}
43+
44+
/**
45+
* Generate a HMAC-SHA1 hash of the counter number.
46+
*/
47+
private byte[] generateHash(String key, long counter) throws InvalidKeyException, NoSuchAlgorithmException {
48+
byte[] data = new byte[8];
49+
long value = counter;
50+
for (int i = 8; i-- > 0; value >>>= 8) {
51+
data[i] = (byte) value;
52+
}
53+
54+
// Create a HMAC-SHA1 signing key from the shared key
55+
Base32 codec = new Base32();
56+
byte[] decodedKey = codec.decode(key);
57+
SecretKeySpec signKey = new SecretKeySpec(decodedKey, algorithm.getHmacAlgorithm());
58+
Mac mac = Mac.getInstance(algorithm.getHmacAlgorithm());
59+
mac.init(signKey);
60+
61+
// Create a hash of the counter value
62+
return mac.doFinal(data);
63+
}
64+
65+
/**
66+
* Get the n-digit code for a given hash.
67+
*/
68+
private String getDigitsFromHash(byte[] hash) {
69+
int offset = hash[hash.length - 1] & 0xF;
70+
71+
long truncatedHash = 0;
72+
73+
for (int i = 0; i < 4; ++i) {
74+
truncatedHash <<= 8;
75+
truncatedHash |= (hash[offset + i] & 0xFF);
76+
}
77+
78+
truncatedHash &= 0x7FFFFFFF;
79+
truncatedHash %= Math.pow(10, digits);
80+
81+
// Left pad with 0s for a n-digit code
82+
return String.format("%0" + digits + "d", truncatedHash);
83+
}
84+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2019 Sam Stevens
3+
*
4+
* Licensed under the MIT License
5+
*
6+
* https://github.com/samdjstevens v1.7.1
7+
*/
8+
9+
package org.labkey.remoteapi.totp;
10+
11+
public enum HashingAlgorithm {
12+
13+
SHA1("HmacSHA1", "SHA1"), //default
14+
SHA256("HmacSHA256", "SHA256"),
15+
SHA512("HmacSHA512", "SHA512");
16+
17+
private final String hmacAlgorithm;
18+
private final String friendlyName;
19+
20+
HashingAlgorithm(String hmacAlgorithm, String friendlyName) {
21+
this.hmacAlgorithm = hmacAlgorithm;
22+
this.friendlyName = friendlyName;
23+
}
24+
25+
public String getHmacAlgorithm() {
26+
return hmacAlgorithm;
27+
}
28+
29+
public String getFriendlyName() {
30+
return friendlyName;
31+
}
32+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) 2019 Sam Stevens
3+
*
4+
* Licensed under the MIT License
5+
*
6+
* https://github.com/samdjstevens v1.7.1
7+
*/
8+
package org.labkey.remoteapi.totp;
9+
10+
import java.time.Instant;
11+
12+
public class TimeProvider {
13+
public long getTime() throws RuntimeException {
14+
return Instant.now().getEpochSecond();
15+
}
16+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.labkey.remoteapi.totp;
2+
3+
public class TotpManager
4+
{
5+
// Generate a TOTP one-time password based on the default parameters (30 second time step, 6 digits, SHA1)
6+
public static String generateCode(String secretKey) throws CodeGenerationException
7+
{
8+
return generateCode(secretKey, 30, 6, HashingAlgorithm.SHA1);
9+
}
10+
11+
// Generate a TOTP one-time password using custom parameters
12+
public static String generateCode(String secretKey, int timeStep, int digits, HashingAlgorithm hashingAlgorithm) throws CodeGenerationException
13+
{
14+
TimeProvider timeProvider = new TimeProvider();
15+
long currentBucket = Math.floorDiv(timeProvider.getTime(), timeStep);
16+
CodeGenerator codeGenerator = new CodeGenerator(hashingAlgorithm, digits);
17+
18+
return codeGenerator.generate(secretKey, currentBucket);
19+
}
20+
}

0 commit comments

Comments
 (0)