Skip to content

Commit 4f14edd

Browse files
bors[bot]meili-botalallema
authored
Merge #269
269: Changes related to the next Meilisearch release (v0.26.0) r=alallema a=meili-bot Related to this issue: meilisearch/integration-guides#181 This PR: - gathers the changes related to the next Meilisearch release (v0.26.0) so that this package is ready when the official release is out. - should pass the tests against the [latest pre-release of Meilisearch](https://github.com/meilisearch/meilisearch/releases). - might eventually contain test failures until the Meilisearch v0.26.0 is out. ⚠️ This PR should NOT be merged until the next release of Meilisearch (v0.26.0) is out. _This PR is auto-generated for the [pre-release week](https://github.com/meilisearch/integration-guides/blob/master/guides/pre-release-week.md) purpose._ Done: - #271 - #275 - #276 Co-authored-by: meili-bot <[email protected]> Co-authored-by: bors[bot] <26634292+bors[bot]@users.noreply.github.com> Co-authored-by: alallema <[email protected]> Co-authored-by: Amélie <[email protected]>
2 parents 40b3c73 + 398ad3d commit 4f14edd

File tree

12 files changed

+627
-118
lines changed

12 files changed

+627
-118
lines changed

.code-samples.meilisearch.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,3 +583,21 @@ authorization_header_1: |-
583583
ApiKey: "masterKey",
584584
})
585585
client.GetKeys();
586+
tenant_token_guide_generate_sdk_1: |-
587+
searchRules := map[string]interface{}{
588+
"patient_medical_records": map[string]string{
589+
"filter": "user_id = 1",
590+
},
591+
}
592+
options := &meilisearch.TenantTokenOptions{
593+
APIKey: "B5KdX2MY2jV6EXfUs6scSfmC...",
594+
ExpiresAt: time.Date(2025, time.December, 20, 0, 0, 0, 0, time.UTC),
595+
}
596+
597+
token, err := client.GenerateTenantToken(searchRules, options);
598+
tenant_token_guide_search_sdk_1: |-
599+
frontEndClient := meilisearch.NewClient(meilisearch.ClientConfig{
600+
Host: "http://127.0.0.1:7700",
601+
APIKey: token,
602+
})
603+
frontEndClient.Index("patient_medical_records").Search("blood test", &meilisearch.SearchRequest{});

.github/workflows/pre-release-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ jobs:
4040
- name: Get the latest Meilisearch RC
4141
run: echo "MEILISEARCH_VERSION=$(curl https://raw.githubusercontent.com/meilisearch/integration-guides/main/scripts/get-latest-meilisearch-rc.sh | bash)" >> $GITHUB_ENV
4242
- name: Meilisearch (${{ env.MEILISEARCH_VERSION }}) setup with Docker
43-
run: docker run -d -p 7700:7700 getmeili/meilisearch:${{ env.MEILISEARCH_VERSION }} ./meilisearch --master-key=masterKey --no-analytics=true
43+
run: docker run -d -p 7700:7700 getmeili/meilisearch:${{ env.MEILISEARCH_VERSION }} ./meilisearch --master-key=masterKey --no-analytics
4444
- name: Run integration tests
4545
run: go test -v ./...

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,6 @@ jobs:
5959
dep ensure
6060
fi
6161
- name: Meilisearch setup (latest version) with Docker
62-
run: docker run -d -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --master-key=masterKey --no-analytics=true
62+
run: docker run -d -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --master-key=masterKey --no-analytics
6363
- name: Run integration tests
6464
run: go test -v ./...

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Each PR should pass the tests and the linter to be accepted.
3939
```bash
4040
# Tests
4141
curl -L https://install.meilisearch.com | sh # download Meilisearch
42-
./meilisearch --master-key=masterKey --no-analytics=true # run Meilisearch
42+
./meilisearch --master-key=masterKey --no-analytics # run Meilisearch
4343
go clean -cache ; go test -v ./...
4444
# Use golangci-lint
4545
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.42.0 golangci-lint run -v

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ searchRes, err := index.Search("wonder",
227227

228228
## 🤖 Compatibility with Meilisearch
229229

230-
This package only guarantees the compatibility with the [version v0.25.0 of Meilisearch](https://github.com/meilisearch/meilisearch/releases/tag/v0.25.0).
230+
This package only guarantees the compatibility with the [version v0.26.0 of Meilisearch](https://github.com/meilisearch/meilisearch/releases/tag/v0.26.0).
231231

232232
## 💡 Learn More
233233

client.go

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package meilisearch
22

33
import (
44
"context"
5+
"fmt"
56
"net/http"
67
"strconv"
78
"time"
89

10+
"github.com/golang-jwt/jwt"
911
"github.com/valyala/fasthttp"
1012
)
1113

@@ -52,6 +54,7 @@ type ClientInterface interface {
5254
GetTask(taskID int64) (resp *Task, err error)
5355
GetTasks() (resp *ResultTask, err error)
5456
WaitForTask(task *Task, options ...WaitParams) (*Task, error)
57+
GenerateTenantToken(searchRules map[string]interface{}, options *TenantTokenOptions) (resp string, err error)
5558
}
5659

5760
var _ ClientInterface = &Client{}
@@ -284,7 +287,8 @@ func (c *Client) GetTasks() (resp *ResultTask, err error) {
284287
return resp, nil
285288
}
286289

287-
// WaitForTask waits for a task to be processed.
290+
// WaitForTask waits for a task to be processed
291+
//
288292
// The function will check by regular interval provided in parameter interval
289293
// the TaskStatus.
290294
// If no ctx and interval are provided WaitForTask will check each 50ms the
@@ -315,7 +319,54 @@ func (c *Client) WaitForTask(task *Task, options ...WaitParams) (*Task, error) {
315319
}
316320
}
317321

318-
// This function allows the user to create a Key with an ExpiredAt in time.Time
322+
// Generate a JWT token for the use of multitenancy
323+
//
324+
// SearchRules parameters is mandatory and should contains the rules to be enforced at search time for all or specific
325+
// accessible indexes for the signing API Key.
326+
// ExpiresAt options is a time.Time when the key will expire. Note that if an ExpiresAt value is included it should be in UTC time.
327+
// ApiKey options is the API key parent of the token. If you leave it empty the client API Key will be used.
328+
func (c *Client) GenerateTenantToken(SearchRules map[string]interface{}, Options *TenantTokenOptions) (resp string, err error) {
329+
// Validate the arguments
330+
if SearchRules == nil {
331+
return "", fmt.Errorf("GenerateTenantToken: The search rules added in the token generation must be of type array or object")
332+
}
333+
if (Options == nil || Options.APIKey == "") && c.config.APIKey == "" {
334+
return "", fmt.Errorf("GenerateTenantToken: The API key used for the token generation must exist and be a valid Meilisearch key")
335+
}
336+
if Options != nil && !Options.ExpiresAt.IsZero() && Options.ExpiresAt.Before(time.Now()) {
337+
return "", fmt.Errorf("GenerateTenantToken: When the expiresAt field in the token generation has a value, it must be a date set in the future")
338+
}
339+
340+
var secret string
341+
if Options == nil || Options.APIKey == "" {
342+
secret = c.config.APIKey
343+
} else {
344+
secret = Options.APIKey
345+
}
346+
347+
// For HMAC signing method, the key should be any []byte
348+
hmacSampleSecret := []byte(secret)
349+
350+
// Create the claims
351+
claims := TenantTokenClaims{}
352+
if Options != nil && !Options.ExpiresAt.IsZero() {
353+
claims.StandardClaims = jwt.StandardClaims{
354+
ExpiresAt: Options.ExpiresAt.Unix(),
355+
}
356+
}
357+
claims.APIKeyPrefix = secret[:8]
358+
claims.SearchRules = SearchRules
359+
360+
// Create a new token object, specifying signing method and the claims
361+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
362+
363+
// Sign and get the complete encoded token as a string using the secret
364+
tokenString, err := token.SignedString(hmacSampleSecret)
365+
366+
return tokenString, err
367+
}
368+
369+
// This function allows the user to create a Key with an ExpiresAt in time.Time
319370
// and transform the Key structure into a KeyParsed structure to send the time format
320371
// managed by Meilisearch
321372
func convertKeyToParsedKey(key Key) (resp KeyParsed) {

client_test.go

Lines changed: 197 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -441,11 +441,10 @@ func TestClient_DeleteKey(t *testing.T) {
441441

442442
func TestClient_Health(t *testing.T) {
443443
tests := []struct {
444-
name string
445-
client *Client
446-
wantResp *Health
447-
wantErr bool
448-
expectedError Error
444+
name string
445+
client *Client
446+
wantResp *Health
447+
wantErr bool
449448
}{
450449
{
451450
name: "TestHealth",
@@ -891,3 +890,196 @@ func TestClient_WaitForTaskWithContext(t *testing.T) {
891890
})
892891
}
893892
}
893+
894+
func TestClient_GenerateTenantToken(t *testing.T) {
895+
type args struct {
896+
UID string
897+
client *Client
898+
searchRules map[string]interface{}
899+
options *TenantTokenOptions
900+
filter []string
901+
}
902+
tests := []struct {
903+
name string
904+
args args
905+
wantErr bool
906+
wantFilter bool
907+
}{
908+
{
909+
name: "TestDefaultGenerateTenantToken",
910+
args: args{
911+
UID: "TestDefaultGenerateTenantToken",
912+
client: privateClient,
913+
searchRules: map[string]interface{}{
914+
"*": map[string]string{},
915+
},
916+
options: nil,
917+
filter: nil,
918+
},
919+
wantErr: false,
920+
wantFilter: false,
921+
},
922+
{
923+
name: "TestGenerateTenantTokenWithApiKey",
924+
args: args{
925+
UID: "TestGenerateTenantTokenWithApiKey",
926+
client: defaultClient,
927+
searchRules: map[string]interface{}{
928+
"*": map[string]string{},
929+
},
930+
options: &TenantTokenOptions{
931+
APIKey: GetPrivateKey(),
932+
},
933+
filter: nil,
934+
},
935+
wantErr: false,
936+
wantFilter: false,
937+
},
938+
{
939+
name: "TestGenerateTenantTokenWithOnlyExpiresAt",
940+
args: args{
941+
UID: "TestGenerateTenantTokenWithOnlyExpiresAt",
942+
client: privateClient,
943+
searchRules: map[string]interface{}{
944+
"*": map[string]string{},
945+
},
946+
options: &TenantTokenOptions{
947+
ExpiresAt: time.Now().Add(time.Hour * 10),
948+
},
949+
filter: nil,
950+
},
951+
wantErr: false,
952+
wantFilter: false,
953+
},
954+
{
955+
name: "TestGenerateTenantTokenWithApiKeyAndExpiresAt",
956+
args: args{
957+
UID: "TestGenerateTenantTokenWithApiKeyAndExpiresAt",
958+
client: defaultClient,
959+
searchRules: map[string]interface{}{
960+
"*": map[string]string{},
961+
},
962+
options: &TenantTokenOptions{
963+
APIKey: GetPrivateKey(),
964+
ExpiresAt: time.Now().Add(time.Hour * 10),
965+
},
966+
filter: nil,
967+
},
968+
wantErr: false,
969+
wantFilter: false,
970+
},
971+
{
972+
name: "TestGenerateTenantTokenWithFilters",
973+
args: args{
974+
UID: "indexUID",
975+
client: privateClient,
976+
searchRules: map[string]interface{}{
977+
"*": map[string]string{
978+
"filter": "book_id > 1000",
979+
},
980+
},
981+
options: nil,
982+
filter: []string{
983+
"book_id",
984+
},
985+
},
986+
wantErr: false,
987+
wantFilter: true,
988+
},
989+
{
990+
name: "TestGenerateTenantTokenWithFilterOnOneINdex",
991+
args: args{
992+
UID: "indexUID",
993+
client: privateClient,
994+
searchRules: map[string]interface{}{
995+
"indexUID": map[string]string{
996+
"filter": "year > 2000",
997+
},
998+
},
999+
options: nil,
1000+
filter: []string{
1001+
"year",
1002+
},
1003+
},
1004+
wantErr: false,
1005+
wantFilter: true,
1006+
},
1007+
{
1008+
name: "TestGenerateTenantTokenWithoutSearchRules",
1009+
args: args{
1010+
UID: "TestGenerateTenantTokenWithoutSearchRules",
1011+
client: privateClient,
1012+
searchRules: nil,
1013+
options: nil,
1014+
filter: nil,
1015+
},
1016+
wantErr: true,
1017+
wantFilter: false,
1018+
},
1019+
{
1020+
name: "TestGenerateTenantTokenWithoutApiKey",
1021+
args: args{
1022+
UID: "TestGenerateTenantTokenWithoutApiKey",
1023+
client: NewClient(ClientConfig{
1024+
Host: "http://localhost:7700",
1025+
APIKey: "",
1026+
}),
1027+
searchRules: map[string]interface{}{
1028+
"*": map[string]string{},
1029+
},
1030+
options: nil,
1031+
filter: nil,
1032+
},
1033+
wantErr: true,
1034+
wantFilter: false,
1035+
},
1036+
{
1037+
name: "TestGenerateTenantTokenWithBadExpiresAt",
1038+
args: args{
1039+
UID: "TestGenerateTenantTokenWithBadExpiresAt",
1040+
client: defaultClient,
1041+
searchRules: map[string]interface{}{
1042+
"*": map[string]string{},
1043+
},
1044+
options: &TenantTokenOptions{
1045+
ExpiresAt: time.Now().Add(-time.Hour * 10),
1046+
},
1047+
filter: nil,
1048+
},
1049+
wantErr: true,
1050+
wantFilter: false,
1051+
},
1052+
}
1053+
for _, tt := range tests {
1054+
t.Run(tt.name, func(t *testing.T) {
1055+
c := tt.args.client
1056+
t.Cleanup(cleanup(c))
1057+
1058+
token, err := c.GenerateTenantToken(tt.args.searchRules, tt.args.options)
1059+
1060+
if tt.wantErr {
1061+
require.Error(t, err)
1062+
} else {
1063+
require.NoError(t, err)
1064+
1065+
if tt.wantFilter {
1066+
gotTask, err := c.Index(tt.args.UID).UpdateFilterableAttributes(&tt.args.filter)
1067+
require.NoError(t, err, "UpdateFilterableAttributes() in TestGenerateTenantToken error should be nil")
1068+
testWaitForTask(t, c.Index(tt.args.UID), gotTask)
1069+
} else {
1070+
_, err := SetUpEmptyIndex(&IndexConfig{Uid: tt.args.UID})
1071+
require.NoError(t, err, "CreateIndex() in TestGenerateTenantToken error should be nil")
1072+
}
1073+
1074+
client := NewClient(ClientConfig{
1075+
Host: "http://localhost:7700",
1076+
APIKey: token,
1077+
})
1078+
1079+
_, err = client.Index(tt.args.UID).Search("", &SearchRequest{})
1080+
1081+
require.NoError(t, err)
1082+
}
1083+
})
1084+
}
1085+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/meilisearch/meilisearch-go
33
go 1.16
44

55
require (
6+
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
67
github.com/mailru/easyjson v0.7.7
78
github.com/pkg/errors v0.9.1
89
github.com/stretchr/testify v1.7.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY
22
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
33
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
44
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
6+
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
57
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
68
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
79
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=

0 commit comments

Comments
 (0)