7
7
import com .algolia .config .*;
8
8
import com .algolia .config .ClientOptions ;
9
9
import com .algolia .exceptions .*;
10
+ import com .algolia .internal .JsonSerializer ;
10
11
import com .algolia .model .search .*;
11
12
import com .algolia .utils .*;
12
13
import com .fasterxml .jackson .core .type .TypeReference ;
14
+ import java .nio .charset .Charset ;
15
+ import java .security .InvalidKeyException ;
16
+ import java .security .NoSuchAlgorithmException ;
17
+ import java .time .Duration ;
18
+ import java .time .Instant ;
13
19
import java .util .ArrayList ;
20
+ import java .util .Base64 ;
14
21
import java .util .Collections ;
15
22
import java .util .EnumSet ;
23
+ import java .util .HashMap ;
16
24
import java .util .List ;
17
25
import java .util .Map ;
18
26
import java .util .Random ;
19
27
import java .util .concurrent .CompletableFuture ;
20
28
import java .util .function .IntUnaryOperator ;
29
+ import java .util .regex .*;
21
30
import java .util .stream .Collectors ;
22
31
import java .util .stream .Stream ;
23
32
import javax .annotation .Nonnull ;
33
+ import javax .crypto .Mac ;
34
+ import javax .crypto .spec .SecretKeySpec ;
24
35
25
36
public class SearchClient extends ApiClient {
26
37
@@ -5856,10 +5867,10 @@ public GetApiKeyResponse waitForApiKey(
5856
5867
}
5857
5868
5858
5869
/**
5859
- * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`.
5870
+ * Helper: Wait for an API key to be added or deleted based on a given `operation`.
5860
5871
*
5861
5872
* @param operation The `operation` that was done on a `key`. (ADD or DELETE only)
5862
- * @param key The `key` that has been added, deleted or updated .
5873
+ * @param key The `key` that has been added or deleted .
5863
5874
* @param maxRetries The maximum number of retry. 50 by default. (optional)
5864
5875
* @param timeout The function to decide how long to wait between retries. min(retries * 200,
5865
5876
* 5000) by default. (optional)
@@ -5891,10 +5902,10 @@ public GetApiKeyResponse waitForApiKey(ApiKeyOperation operation, String key, Ap
5891
5902
}
5892
5903
5893
5904
/**
5894
- * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`.
5905
+ * Helper: Wait for an API key to be added or deleted based on a given `operation`.
5895
5906
*
5896
5907
* @param operation The `operation` that was done on a `key`. (ADD or DELETE only)
5897
- * @param key The `key` that has been added, deleted or updated .
5908
+ * @param key The `key` that has been added or deleted .
5898
5909
* @param requestOptions The requestOptions to send along with the query, they will be merged with
5899
5910
* the transporter requestOptions. (optional)
5900
5911
*/
@@ -5918,10 +5929,10 @@ public GetApiKeyResponse waitForApiKey(ApiKeyOperation operation, String key, Ap
5918
5929
}
5919
5930
5920
5931
/**
5921
- * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`.
5932
+ * Helper: Wait for an API key to be added or deleted based on a given `operation`.
5922
5933
*
5923
5934
* @param operation The `operation` that was done on a `key`. (ADD or DELETE only)
5924
- * @param key The `key` that has been added, deleted or updated .
5935
+ * @param key The `key` that has been added or deleted .
5925
5936
* @param maxRetries The maximum number of retry. 50 by default. (optional)
5926
5937
* @param timeout The function to decide how long to wait between retries. min(retries * 200,
5927
5938
* 5000) by default. (optional)
@@ -5943,10 +5954,10 @@ public GetApiKeyResponse waitForApiKey(ApiKeyOperation operation, String key, Ap
5943
5954
}
5944
5955
5945
5956
/**
5946
- * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`.
5957
+ * Helper: Wait for an API key to be added or deleted based on a given `operation`.
5947
5958
*
5948
5959
* @param operation The `operation` that was done on a `key`. (ADD or DELETE only)
5949
- * @param key The `key` that has been added, deleted or updated .
5960
+ * @param key The `key` that has been added or deleted .
5950
5961
*/
5951
5962
public GetApiKeyResponse waitForApiKey (ApiKeyOperation operation , String key ) {
5952
5963
return this .waitForApiKey (operation , key , null , TaskUtils .DEFAULT_MAX_RETRIES , TaskUtils .DEFAULT_TIMEOUT , null );
@@ -6353,4 +6364,89 @@ public <T> ReplaceAllObjectsResponse replaceAllObjects(
6353
6364
.setBatchResponses (batchResponses )
6354
6365
.setMoveOperationResponse (moveOperationResponse );
6355
6366
}
6367
+
6368
+ /**
6369
+ * Helper: Generates a secured API key based on the given `parent_api_key` and given
6370
+ * `restrictions`.
6371
+ *
6372
+ * @param parentApiKey API key to generate from.
6373
+ * @param restrictions Restrictions to add the key
6374
+ * @throws Exception if an error occurs during the encoding
6375
+ * @throws AlgoliaRetryException When the retry has failed on all hosts
6376
+ * @throws AlgoliaApiException When the API sends an http error code
6377
+ * @throws AlgoliaRuntimeException When an error occurred during the serialization
6378
+ */
6379
+ public String generateSecuredApiKey (@ Nonnull String parentApiKey , @ Nonnull SecuredAPIKeyRestrictions restrictions ) throws Exception {
6380
+ Map <String , String > restrictionsMap = new HashMap <>();
6381
+ if (restrictions .getFilters () != null ) restrictionsMap .put ("filters" , StringUtils .paramToString (restrictions .getFilters ()));
6382
+ if (restrictions .getValidUntil () != 0 ) restrictionsMap .put ("validUntil" , StringUtils .paramToString (restrictions .getValidUntil ()));
6383
+ if (restrictions .getRestrictIndices () != null ) restrictionsMap .put (
6384
+ "restrictIndices" ,
6385
+ StringUtils .paramToString (restrictions .getRestrictIndices ())
6386
+ );
6387
+ if (restrictions .getRestrictSources () != null ) restrictionsMap .put (
6388
+ "restrictSources" ,
6389
+ StringUtils .paramToString (restrictions .getRestrictSources ())
6390
+ );
6391
+ if (restrictions .getUserToken () != null ) restrictionsMap .put ("userToken" , StringUtils .paramToString (restrictions .getUserToken ()));
6392
+
6393
+ if (restrictions .getSearchParams () != null ) {
6394
+ Map <String , Object > searchParamsMap = JsonSerializer
6395
+ .getObjectMapper ()
6396
+ .convertValue (restrictions .getSearchParams (), new TypeReference <Map <String , Object >>() {});
6397
+ searchParamsMap .forEach ((key , value ) -> restrictionsMap .put (key , StringUtils .paramToString (value )));
6398
+ }
6399
+
6400
+ String queryStr = restrictionsMap
6401
+ .entrySet ()
6402
+ .stream ()
6403
+ .sorted (Map .Entry .comparingByKey ())
6404
+ .map (entry -> String .format ("%s=%s" , entry .getKey (), entry .getValue ()))
6405
+ .collect (Collectors .joining ("&" ));
6406
+
6407
+ String key = hmac (parentApiKey , queryStr );
6408
+
6409
+ return new String (Base64 .getEncoder ().encode (String .format ("%s%s" , key , queryStr ).getBytes (Charset .forName ("UTF8" ))));
6410
+ }
6411
+
6412
+ private String hmac (String key , String msg ) throws NoSuchAlgorithmException , InvalidKeyException {
6413
+ Mac hmac = Mac .getInstance ("HmacSHA256" );
6414
+ hmac .init (new SecretKeySpec (key .getBytes (), "HmacSHA256" ));
6415
+ byte [] rawHmac = hmac .doFinal (msg .getBytes ());
6416
+ StringBuilder sb = new StringBuilder (rawHmac .length * 2 );
6417
+ for (byte b : rawHmac ) {
6418
+ sb .append (String .format ("%02x" , b & 0xff ));
6419
+ }
6420
+ return sb .toString ();
6421
+ }
6422
+
6423
+ /**
6424
+ * Helper: Retrieves the remaining validity of the previous generated `secured_api_key`, the
6425
+ * `validUntil` parameter must have been provided.
6426
+ *
6427
+ * @param securedApiKey The secured API Key to check
6428
+ * @throws AlgoliaRuntimeException if <code>securedApiKey</code> is null, empty or whitespaces.
6429
+ * @throws AlgoliaRuntimeException if <code>securedApiKey</code> doesn't have a <code>validUntil
6430
+ * </code> parameter.
6431
+ */
6432
+ public Duration getSecuredApiKeyRemainingValidity (@ Nonnull String securedApiKey ) {
6433
+ if (securedApiKey == null || securedApiKey .trim ().isEmpty ()) {
6434
+ throw new AlgoliaRuntimeException ("securedAPIKey must not be empty, null or whitespaces" );
6435
+ }
6436
+
6437
+ byte [] decodedBytes = Base64 .getDecoder ().decode (securedApiKey );
6438
+ String decodedString = new String (decodedBytes );
6439
+
6440
+ Pattern pattern = Pattern .compile ("validUntil=\\ d+" );
6441
+ Matcher matcher = pattern .matcher (decodedString );
6442
+
6443
+ if (!matcher .find ()) {
6444
+ throw new AlgoliaRuntimeException ("The Secured API Key doesn't have a validUntil parameter." );
6445
+ }
6446
+
6447
+ String validUntilMatch = matcher .group (0 );
6448
+ long timeStamp = Long .parseLong (validUntilMatch .replace ("validUntil=" , "" ));
6449
+
6450
+ return Duration .ofSeconds (timeStamp - Instant .now ().getEpochSecond ());
6451
+ }
6356
6452
}
0 commit comments