Skip to content

Commit

Permalink
Merge pull request #315 from meilisearch/generate_token
Browse files Browse the repository at this point in the history
Apply tenant changes
  • Loading branch information
alallema authored Jul 5, 2022
2 parents c3928e2 + 40839ab commit 628d9d5
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 27 deletions.
15 changes: 12 additions & 3 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"regexp"
"strconv"
"time"

Expand Down Expand Up @@ -53,7 +54,7 @@ type ClientInterface interface {
GetTask(taskID int64) (resp *Task, err error)
GetTasks() (resp *ResultTask, err error)
WaitForTask(task *Task, options ...WaitParams) (*Task, error)
GenerateTenantToken(searchRules map[string]interface{}, options *TenantTokenOptions) (resp string, err error)
GenerateTenantToken(APIKeyUID string, searchRules map[string]interface{}, options *TenantTokenOptions) (resp string, err error)
}

var _ ClientInterface = &Client{}
Expand Down Expand Up @@ -333,14 +334,17 @@ func (c *Client) WaitForTask(task *Task, options ...WaitParams) (*Task, error) {
// accessible indexes for the signing API Key.
// 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.
// ApiKey options is the API key parent of the token. If you leave it empty the client API Key will be used.
func (c *Client) GenerateTenantToken(SearchRules map[string]interface{}, Options *TenantTokenOptions) (resp string, err error) {
func (c *Client) GenerateTenantToken(APIKeyUID string, SearchRules map[string]interface{}, Options *TenantTokenOptions) (resp string, err error) {
// Validate the arguments
if SearchRules == nil {
return "", fmt.Errorf("GenerateTenantToken: The search rules added in the token generation must be of type array or object")
}
if (Options == nil || Options.APIKey == "") && c.config.APIKey == "" {
return "", fmt.Errorf("GenerateTenantToken: The API key used for the token generation must exist and be a valid Meilisearch key")
}
if APIKeyUID == "" || !IsValidUUID(APIKeyUID) {
return "", fmt.Errorf("GenerateTenantToken: The uid used for the token generation must exist and comply to uuid4 format")
}
if Options != nil && !Options.ExpiresAt.IsZero() && Options.ExpiresAt.Before(time.Now()) {
return "", fmt.Errorf("GenerateTenantToken: When the expiresAt field in the token generation has a value, it must be a date set in the future")
}
Expand All @@ -362,7 +366,7 @@ func (c *Client) GenerateTenantToken(SearchRules map[string]interface{}, Options
ExpiresAt: Options.ExpiresAt.Unix(),
}
}
claims.APIKeyPrefix = secret[:8]
claims.APIKeyUID = APIKeyUID
claims.SearchRules = SearchRules

// Create a new token object, specifying signing method and the claims
Expand All @@ -389,3 +393,8 @@ func convertKeyToParsedKey(key Key) (resp KeyParsed) {
}
return resp
}

func IsValidUUID(uuid string) bool {
r := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")
return r.MatchString(uuid)
}
84 changes: 62 additions & 22 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -887,8 +887,9 @@ func TestClient_ConnectionCloseByServer(t *testing.T) {

func TestClient_GenerateTenantToken(t *testing.T) {
type args struct {
UID string
IndexUID string
client *Client
APIKeyUID string
searchRules map[string]interface{}
options *TenantTokenOptions
filter []string
Expand All @@ -902,8 +903,9 @@ func TestClient_GenerateTenantToken(t *testing.T) {
{
name: "TestDefaultGenerateTenantToken",
args: args{
UID: "TestDefaultGenerateTenantToken",
client: privateClient,
IndexUID: "TestDefaultGenerateTenantToken",
client: privateClient,
APIKeyUID: GetPrivateUIDKey(),
searchRules: map[string]interface{}{
"*": map[string]string{},
},
Expand All @@ -916,8 +918,9 @@ func TestClient_GenerateTenantToken(t *testing.T) {
{
name: "TestGenerateTenantTokenWithApiKey",
args: args{
UID: "TestGenerateTenantTokenWithApiKey",
client: defaultClient,
IndexUID: "TestGenerateTenantTokenWithApiKey",
client: defaultClient,
APIKeyUID: GetPrivateUIDKey(),
searchRules: map[string]interface{}{
"*": map[string]string{},
},
Expand All @@ -932,8 +935,9 @@ func TestClient_GenerateTenantToken(t *testing.T) {
{
name: "TestGenerateTenantTokenWithOnlyExpiresAt",
args: args{
UID: "TestGenerateTenantTokenWithOnlyExpiresAt",
client: privateClient,
IndexUID: "TestGenerateTenantTokenWithOnlyExpiresAt",
client: privateClient,
APIKeyUID: GetPrivateUIDKey(),
searchRules: map[string]interface{}{
"*": map[string]string{},
},
Expand All @@ -948,8 +952,9 @@ func TestClient_GenerateTenantToken(t *testing.T) {
{
name: "TestGenerateTenantTokenWithApiKeyAndExpiresAt",
args: args{
UID: "TestGenerateTenantTokenWithApiKeyAndExpiresAt",
client: defaultClient,
IndexUID: "TestGenerateTenantTokenWithApiKeyAndExpiresAt",
client: defaultClient,
APIKeyUID: GetPrivateUIDKey(),
searchRules: map[string]interface{}{
"*": map[string]string{},
},
Expand All @@ -965,8 +970,9 @@ func TestClient_GenerateTenantToken(t *testing.T) {
{
name: "TestGenerateTenantTokenWithFilters",
args: args{
UID: "indexUID",
client: privateClient,
IndexUID: "indexUID",
client: privateClient,
APIKeyUID: GetPrivateUIDKey(),
searchRules: map[string]interface{}{
"*": map[string]string{
"filter": "book_id > 1000",
Expand All @@ -983,8 +989,9 @@ func TestClient_GenerateTenantToken(t *testing.T) {
{
name: "TestGenerateTenantTokenWithFilterOnOneINdex",
args: args{
UID: "indexUID",
client: privateClient,
IndexUID: "indexUID",
client: privateClient,
APIKeyUID: GetPrivateUIDKey(),
searchRules: map[string]interface{}{
"indexUID": map[string]string{
"filter": "year > 2000",
Expand All @@ -1001,8 +1008,9 @@ func TestClient_GenerateTenantToken(t *testing.T) {
{
name: "TestGenerateTenantTokenWithoutSearchRules",
args: args{
UID: "TestGenerateTenantTokenWithoutSearchRules",
IndexUID: "TestGenerateTenantTokenWithoutSearchRules",
client: privateClient,
APIKeyUID: GetPrivateUIDKey(),
searchRules: nil,
options: nil,
filter: nil,
Expand All @@ -1013,11 +1021,12 @@ func TestClient_GenerateTenantToken(t *testing.T) {
{
name: "TestGenerateTenantTokenWithoutApiKey",
args: args{
UID: "TestGenerateTenantTokenWithoutApiKey",
IndexUID: "TestGenerateTenantTokenWithoutApiKey",
client: NewClient(ClientConfig{
Host: "http://localhost:7700",
APIKey: "",
}),
APIKeyUID: GetPrivateUIDKey(),
searchRules: map[string]interface{}{
"*": map[string]string{},
},
Expand All @@ -1030,8 +1039,9 @@ func TestClient_GenerateTenantToken(t *testing.T) {
{
name: "TestGenerateTenantTokenWithBadExpiresAt",
args: args{
UID: "TestGenerateTenantTokenWithBadExpiresAt",
client: defaultClient,
IndexUID: "TestGenerateTenantTokenWithBadExpiresAt",
client: defaultClient,
APIKeyUID: GetPrivateUIDKey(),
searchRules: map[string]interface{}{
"*": map[string]string{},
},
Expand All @@ -1043,25 +1053,55 @@ func TestClient_GenerateTenantToken(t *testing.T) {
wantErr: true,
wantFilter: false,
},
{
name: "TestGenerateTenantTokenWithBadAPIKeyUID",
args: args{
IndexUID: "TestGenerateTenantTokenWithBadAPIKeyUID",
client: defaultClient,
APIKeyUID: GetPrivateUIDKey() + "1234",
searchRules: map[string]interface{}{
"*": map[string]string{},
},
options: nil,
filter: nil,
},
wantErr: true,
wantFilter: false,
},
{
name: "TestGenerateTenantTokenWithEmptyAPIKeyUID",
args: args{
IndexUID: "TestGenerateTenantTokenWithEmptyAPIKeyUID",
client: defaultClient,
APIKeyUID: "",
searchRules: map[string]interface{}{
"*": map[string]string{},
},
options: nil,
filter: nil,
},
wantErr: true,
wantFilter: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := tt.args.client
t.Cleanup(cleanup(c))

token, err := c.GenerateTenantToken(tt.args.searchRules, tt.args.options)
token, err := c.GenerateTenantToken(tt.args.APIKeyUID, tt.args.searchRules, tt.args.options)

if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)

if tt.wantFilter {
gotTask, err := c.Index(tt.args.UID).UpdateFilterableAttributes(&tt.args.filter)
gotTask, err := c.Index(tt.args.IndexUID).UpdateFilterableAttributes(&tt.args.filter)
require.NoError(t, err, "UpdateFilterableAttributes() in TestGenerateTenantToken error should be nil")
testWaitForTask(t, c.Index(tt.args.UID), gotTask)
testWaitForTask(t, c.Index(tt.args.IndexUID), gotTask)
} else {
_, err := SetUpEmptyIndex(&IndexConfig{Uid: tt.args.UID})
_, err := SetUpEmptyIndex(&IndexConfig{Uid: tt.args.IndexUID})
require.NoError(t, err, "CreateIndex() in TestGenerateTenantToken error should be nil")
}

Expand All @@ -1070,7 +1110,7 @@ func TestClient_GenerateTenantToken(t *testing.T) {
APIKey: token,
})

_, err = client.Index(tt.args.UID).Search("", &SearchRequest{})
_, err = client.Index(tt.args.IndexUID).Search("", &SearchRequest{})

require.NoError(t, err)
}
Expand Down
13 changes: 13 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ func GetPrivateKey() (key string) {
return ""
}

func GetPrivateUIDKey() (key string) {
list, err := defaultClient.GetKeys(nil)
if err != nil {
return ""
}
for _, key := range list.Results {
if strings.Contains(key.Name, "Default Admin API Key") || (key.Description == "") {
return key.UID
}
}
return ""
}

func SetUpEmptyIndex(index *IndexConfig) (resp *Index, err error) {
client := NewClient(ClientConfig{
Host: "http://localhost:7700",
Expand Down
4 changes: 2 additions & 2 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ type TenantTokenOptions struct {

// Custom Claims structure to create a Tenant Token
type TenantTokenClaims struct {
APIKeyPrefix string `json:"apiKeyPrefix"`
SearchRules interface{} `json:"searchRules"`
APIKeyUID string `json:"apiKeyUid"`
SearchRules interface{} `json:"searchRules"`
jwt.StandardClaims
}

Expand Down

0 comments on commit 628d9d5

Please sign in to comment.