Mainflux.mainflux/auth/service_test.go

534 lines
16 KiB
Go

// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package auth_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/auth/jwt"
"github.com/mainflux/mainflux/auth/mocks"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mainflux/mainflux/pkg/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
var idProvider = uuid.New()
const (
secret = "secret"
email = "test@example.com"
id = "testID"
groupName = "mfx"
description = "Description"
memberRelation = "member"
authoritiesObj = "authorities"
loginDuration = 30 * time.Minute
refreshDuration = 24 * time.Hour
accessToken = "access"
)
func newService() (auth.Service, *mocks.Keys) {
krepo := new(mocks.Keys)
prepo := new(mocks.PolicyAgent)
idProvider := uuid.NewMock()
t := jwt.New([]byte(secret))
return auth.New(krepo, idProvider, t, prepo, loginDuration, refreshDuration), krepo
}
func TestIssue(t *testing.T) {
svc, krepo := newService()
cases := []struct {
desc string
key auth.Key
token string
err error
}{
{
desc: "issue login key",
key: auth.Key{
Type: auth.AccessKey,
IssuedAt: time.Now(),
},
token: accessToken,
err: nil,
},
{
desc: "issue login key with no time",
key: auth.Key{
Type: auth.AccessKey,
},
token: accessToken,
err: auth.ErrInvalidKeyIssuedAt,
},
{
desc: "issue API key",
key: auth.Key{
Type: auth.APIKey,
IssuedAt: time.Now(),
},
token: accessToken,
err: nil,
},
{
desc: "issue API key with an invalid token",
key: auth.Key{
Type: auth.APIKey,
IssuedAt: time.Now(),
},
token: "invalid",
err: errors.ErrAuthentication,
},
{
desc: "issue API key with no time",
key: auth.Key{
Type: auth.APIKey,
},
token: accessToken,
err: auth.ErrInvalidKeyIssuedAt,
},
{
desc: "issue recovery key",
key: auth.Key{
Type: auth.RecoveryKey,
IssuedAt: time.Now(),
},
token: "",
err: nil,
},
{
desc: "issue recovery with no issue time",
key: auth.Key{
Type: auth.RecoveryKey,
},
token: accessToken,
err: auth.ErrInvalidKeyIssuedAt,
},
}
for _, tc := range cases {
repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, tc.err)
_, err := svc.Issue(context.Background(), tc.token, tc.key)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
repocall.Unset()
}
}
func TestRevoke(t *testing.T) {
svc, _ := newService()
secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
Type: auth.APIKey,
IssuedAt: time.Now(),
Subject: id,
}
_, err = svc.Issue(context.Background(), secret.AccessToken, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
cases := []struct {
desc string
id string
token string
err error
}{
{
desc: "revoke login key",
// id: newKey.ID,
token: secret.AccessToken,
err: nil,
},
{
desc: "revoke non-existing login key",
// id: newKey.ID,
token: secret.AccessToken,
err: nil,
},
{
desc: "revoke with empty login key",
// id: newKey.ID,
token: "",
err: errors.ErrAuthentication,
},
}
for _, tc := range cases {
err := svc.Revoke(context.Background(), tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRetrieve(t *testing.T) {
svc, _ := newService()
secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
Subject: id,
IssuedAt: time.Now(),
}
userToken, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
apiToken, err := svc.Issue(context.Background(), secret.AccessToken, key)
assert.Nil(t, err, fmt.Sprintf("Issuing login's key expected to succeed: %s", err))
resetToken, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.RecoveryKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
cases := []struct {
desc string
id string
token string
err error
}{
{
desc: "retrieve login key",
// id: apiKey.ID,
token: userToken.AccessToken,
err: nil,
},
{
desc: "retrieve non-existing login key",
id: "invalid",
token: userToken.AccessToken,
err: errors.ErrNotFound,
},
{
desc: "retrieve with wrong login key",
// id: apiKey.ID,
token: "wrong",
err: errors.ErrAuthentication,
},
{
desc: "retrieve with API token",
// id: apiKey.ID,
token: apiToken.AccessToken,
err: errors.ErrAuthentication,
},
{
desc: "retrieve with reset token",
// id: apiKey.ID,
token: resetToken.AccessToken,
err: errors.ErrAuthentication,
},
}
for _, tc := range cases {
_, err := svc.RetrieveKey(context.Background(), tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestIdentify(t *testing.T) {
svc, _ := newService()
loginSecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
recoverySecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.RecoveryKey, IssuedAt: time.Now(), Subject: id})
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
apiSecret, err := svc.Issue(context.Background(), loginSecret.AccessToken, auth.Key{Type: auth.APIKey, Subject: id, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute)})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
exp1 := time.Now().Add(-2 * time.Second)
expSecret, err := svc.Issue(context.Background(), loginSecret.AccessToken, auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), ExpiresAt: exp1})
assert.Nil(t, err, fmt.Sprintf("Issuing expired login key expected to succeed: %s", err))
invalidSecret, err := svc.Issue(context.Background(), loginSecret.AccessToken, auth.Key{Type: 22, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
cases := []struct {
desc string
key string
idt string
err error
}{
{
desc: "identify login key",
key: loginSecret.AccessToken,
idt: id,
err: nil,
},
{
desc: "identify recovery key",
key: recoverySecret.AccessToken,
idt: id,
err: nil,
},
{
desc: "identify API key",
key: apiSecret.AccessToken,
idt: id,
err: nil,
},
{
desc: "identify expired API key",
key: expSecret.AccessToken,
idt: "",
err: auth.ErrAPIKeyExpired,
},
{
desc: "identify expired key",
key: invalidSecret.AccessToken,
idt: "",
err: errors.ErrAuthentication,
},
{
desc: "identify invalid key",
key: "invalid",
idt: "",
err: errors.ErrAuthentication,
},
}
for _, tc := range cases {
idt, err := svc.Identify(context.Background(), tc.key)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.idt, idt, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.idt, idt))
}
}
func TestAuthorize(t *testing.T) {
svc, _ := newService()
pr := auth.PolicyReq{Object: authoritiesObj, Relation: memberRelation, Subject: id}
err := svc.Authorize(context.Background(), pr)
require.Nil(t, err, fmt.Sprintf("authorizing initial %v policy expected to succeed: %s", pr, err))
}
func TestAddPolicy(t *testing.T) {
svc, _ := newService()
pr := auth.PolicyReq{Object: "obj", Relation: "rel", Subject: "sub"}
err := svc.AddPolicy(context.Background(), pr)
require.Nil(t, err, fmt.Sprintf("adding %v policy expected to succeed: %v", pr, err))
err = svc.Authorize(context.Background(), pr)
require.Nil(t, err, fmt.Sprintf("checking shared %v policy expected to be succeed: %#v", pr, err))
}
func TestDeletePolicy(t *testing.T) {
svc, _ := newService()
pr := auth.PolicyReq{Object: authoritiesObj, Relation: memberRelation, Subject: id}
err := svc.DeletePolicy(context.Background(), pr)
require.Nil(t, err, fmt.Sprintf("deleting %v policy expected to succeed: %s", pr, err))
}
func TestAddPolicies(t *testing.T) {
svc, _ := newService()
secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
Subject: id,
IssuedAt: time.Now(),
}
apiToken, err := svc.Issue(context.Background(), secret.AccessToken, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
thingID, err := idProvider.ID()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
tmpID := "tmpid"
readPolicy := "read"
writePolicy := "write"
deletePolicy := "delete"
// Add read policy to users.
err = svc.AddPolicies(context.Background(), apiToken.AccessToken, thingID, []string{id, tmpID}, []string{readPolicy})
assert.Nil(t, err, fmt.Sprintf("adding policies expected to succeed: %s", err))
// Add write and delete policies to users.
err = svc.AddPolicies(context.Background(), apiToken.AccessToken, thingID, []string{id, tmpID}, []string{writePolicy, deletePolicy})
assert.Nil(t, err, fmt.Sprintf("adding multiple policies expected to succeed: %s", err))
cases := []struct {
desc string
policy auth.PolicyReq
err error
}{
{
desc: "check valid 'read' policy of user with id",
policy: auth.PolicyReq{Object: thingID, Relation: readPolicy, Subject: id},
err: nil,
},
{
desc: "check valid 'write' policy of user with id",
policy: auth.PolicyReq{Object: thingID, Relation: writePolicy, Subject: id},
err: nil,
},
{
desc: "check valid 'delete' policy of user with id",
policy: auth.PolicyReq{Object: thingID, Relation: deletePolicy, Subject: id},
err: nil,
},
{
desc: "check valid 'read' policy of user with tmpid",
policy: auth.PolicyReq{Object: thingID, Relation: readPolicy, Subject: tmpID},
err: nil,
},
{
desc: "check valid 'write' policy of user with tmpid",
policy: auth.PolicyReq{Object: thingID, Relation: writePolicy, Subject: tmpID},
err: nil,
},
{
desc: "check valid 'delete' policy of user with tmpid",
policy: auth.PolicyReq{Object: thingID, Relation: deletePolicy, Subject: tmpID},
err: nil,
},
{
desc: "check invalid 'access' policy of user with id",
policy: auth.PolicyReq{Object: thingID, Relation: "access", Subject: id},
err: errors.ErrAuthorization,
},
{
desc: "check invalid 'access' policy of user with tmpid",
policy: auth.PolicyReq{Object: thingID, Relation: "access", Subject: tmpID},
err: errors.ErrAuthorization,
},
}
for _, tc := range cases {
err := svc.Authorize(context.Background(), tc.policy)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.err, err))
}
}
func TestDeletePolicies(t *testing.T) {
svc, _ := newService()
secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
Subject: id,
IssuedAt: time.Now(),
}
apiToken, err := svc.Issue(context.Background(), secret.AccessToken, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
thingID, err := idProvider.ID()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
tmpID := "tmpid"
readPolicy := "read"
writePolicy := "write"
deletePolicy := "delete"
memberPolicy := "member"
// Add read, write and delete policies to users.
err = svc.AddPolicies(context.Background(), apiToken.AccessToken, thingID, []string{id, tmpID}, []string{readPolicy, writePolicy, deletePolicy, memberPolicy})
assert.Nil(t, err, fmt.Sprintf("adding policies expected to succeed: %s", err))
// Delete multiple policies from single user.
err = svc.DeletePolicies(context.Background(), apiToken.AccessToken, thingID, []string{id}, []string{readPolicy, writePolicy})
assert.Nil(t, err, fmt.Sprintf("deleting policies from single user expected to succeed: %s", err))
// Delete multiple policies from multiple user.
err = svc.DeletePolicies(context.Background(), apiToken.AccessToken, thingID, []string{id, tmpID}, []string{deletePolicy, memberPolicy})
assert.Nil(t, err, fmt.Sprintf("deleting policies from multiple user expected to succeed: %s", err))
cases := []struct {
desc string
policy auth.PolicyReq
err error
}{
{
desc: "check non-existing 'read' policy of user with id",
policy: auth.PolicyReq{Object: thingID, Relation: readPolicy, Subject: id},
err: errors.ErrAuthorization,
},
{
desc: "check non-existing 'write' policy of user with id",
policy: auth.PolicyReq{Object: thingID, Relation: writePolicy, Subject: id},
err: errors.ErrAuthorization,
},
{
desc: "check non-existing 'delete' policy of user with id",
policy: auth.PolicyReq{Object: thingID, Relation: deletePolicy, Subject: id},
err: errors.ErrAuthorization,
},
{
desc: "check non-existing 'member' policy of user with id",
policy: auth.PolicyReq{Object: thingID, Relation: memberPolicy, Subject: id},
err: errors.ErrAuthorization,
},
{
desc: "check non-existing 'delete' policy of user with tmpid",
policy: auth.PolicyReq{Object: thingID, Relation: deletePolicy, Subject: tmpID},
err: errors.ErrAuthorization,
},
{
desc: "check non-existing 'member' policy of user with tmpid",
policy: auth.PolicyReq{Object: thingID, Relation: memberPolicy, Subject: tmpID},
err: errors.ErrAuthorization,
},
{
desc: "check valid 'read' policy of user with tmpid",
policy: auth.PolicyReq{Object: thingID, Relation: readPolicy, Subject: tmpID},
err: nil,
},
{
desc: "check valid 'write' policy of user with tmpid",
policy: auth.PolicyReq{Object: thingID, Relation: writePolicy, Subject: tmpID},
err: nil,
},
}
for _, tc := range cases {
err := svc.Authorize(context.Background(), tc.policy)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.err, err))
}
}
func TestListPolicies(t *testing.T) {
svc, _ := newService()
secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
Subject: id,
IssuedAt: time.Now(),
}
apiToken, err := svc.Issue(context.Background(), secret.AccessToken, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
readPolicy := "read"
pageLen := 15
// Add arbitrary policies to the user.
for i := 0; i < pageLen; i++ {
err = svc.AddPolicies(context.Background(), apiToken.AccessToken, fmt.Sprintf("thing-%d", i), []string{id}, []string{readPolicy})
assert.Nil(t, err, fmt.Sprintf("adding policies expected to succeed: %s", err))
}
page, err := svc.ListObjects(context.Background(), auth.PolicyReq{Subject: id, Relation: readPolicy}, "", 100)
assert.Nil(t, err, fmt.Sprintf("listing policies expected to succeed: %s", err))
assert.Equal(t, pageLen, len(page.Policies), fmt.Sprintf("unexpected listing page size, expected %d, got %d: %v", pageLen, len(page.Policies), err))
}