MG-1987 - Enable entity endpoints to return basic info for non-admins (#2168)

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>
This commit is contained in:
Washington Kigani Kamadi
2024-05-20 13:05:39 +03:00
committed by Dusan Borovcanin
parent cbb97c1d2e
commit 3e70fedb14
9 changed files with 159 additions and 92 deletions
+2 -2
View File
@@ -99,10 +99,10 @@ paths:
responses:
"200":
$ref: "#/components/responses/DomainRes"
"400":
description: Failed due to malformed query parameters.
"401":
description: Missing or invalid access token provided.
"403":
description: Failed to perform authorization over the entity.
"404":
description: A non-existent entity request.
"422":
+10
View File
@@ -57,6 +57,8 @@ paths:
description: Missing or invalid access token provided.
"403":
description: Failed to perform authorization over the entity.
"404":
description: A non-existent entity request.
"409":
description: Failed due to using an existing identity.
"415":
@@ -122,6 +124,8 @@ paths:
description: Missing or invalid access token provided.
"403":
description: Failed to perform authorization over the entity.
"404":
description: A non-existent entity request.
"415":
description: Missing or invalid content type.
"422":
@@ -207,6 +211,8 @@ paths:
description: Missing or invalid access token provided.
"403":
description: Unauthorized access to thing id.
"404":
description: Missing thing.
"500":
$ref: "#/components/responses/ServiceError"
/things/{thingID}/tags:
@@ -446,6 +452,8 @@ paths:
description: Missing or invalid access token provided.
"403":
description: Failed to perform authorization over the entity.
"404":
descripttion: A non-existent entity request.
"409":
description: Failed due to using an existing identity.
"415":
@@ -568,6 +576,8 @@ paths:
description: Missing or invalid access token provided.
"403":
description: Unauthorized access to thing id.
"404":
descripttion: A non-existent entity request.
"500":
$ref: "#/components/responses/ServiceError"
-2
View File
@@ -137,8 +137,6 @@ paths:
description: Failed due to malformed query parameters.
"401":
description: Missing or invalid access token provided.
"403":
description: Failed to perform authorization over the entity.
"404":
description: A non-existent entity request.
"422":
+1 -1
View File
@@ -183,7 +183,7 @@ type DomainsRepository interface {
// RetrievePermissions retrieves domain permissions.
RetrievePermissions(ctx context.Context, subject, id string) ([]string, error)
// RetrieveAllByIDs retrieves for given Domain IDs .
// RetrieveAllByIDs retrieves for given Domain IDs.
RetrieveAllByIDs(ctx context.Context, pm Page) (DomainsPage, error)
// Update updates the client name and metadata.
+15 -11
View File
@@ -570,21 +570,25 @@ func (svc service) CreateDomain(ctx context.Context, token string, d Domain) (do
}
func (svc service) RetrieveDomain(ctx context.Context, token, id string) (Domain, error) {
if err := svc.Authorize(ctx, PolicyReq{
Subject: token,
SubjectType: UserType,
SubjectKind: TokenKind,
Object: id,
ObjectType: DomainType,
Permission: ViewPermission,
}); err != nil {
return Domain{}, errors.Wrap(svcerr.ErrAuthorization, err)
res, err := svc.Identify(ctx, token)
if err != nil {
return Domain{}, errors.Wrap(svcerr.ErrAuthentication, err)
}
dom, err := svc.domains.RetrieveByID(ctx, id)
domain, err := svc.domains.RetrieveByID(ctx, id)
if err != nil {
return Domain{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
return dom, nil
if err = svc.Authorize(ctx, PolicyReq{
Subject: res.Subject,
SubjectType: UserType,
SubjectKind: UsersKind,
Object: id,
ObjectType: DomainType,
Permission: MembershipPermission,
}); err != nil {
return Domain{ID: domain.ID, Name: domain.Name, Alias: domain.Alias}, nil
}
return domain, nil
}
func (svc service) RetrieveDomainPermissions(ctx context.Context, token, id string) (Permissions, error) {
+11 -11
View File
@@ -1161,7 +1161,6 @@ func TestAuthorize(t *testing.T) {
},
}
for _, tc := range cases {
fmt.Println(tc.desc)
repoCall := prepo.On("CheckPolicy", mock.Anything, tc.checkPolicyReq3).Return(tc.checkPolicyErr)
repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(tc.retrieveDomainRes, nil)
repoCall2 := prepo.On("CheckPolicy", mock.Anything, tc.checkAdminPolicyReq).Return(tc.checkPolicyErr1)
@@ -1175,7 +1174,6 @@ func TestAuthorize(t *testing.T) {
repoCall3.Unset()
repoCall4.Unset()
}
fmt.Println("Cases 1 end")
cases2 := []struct {
desc string
policyReq auth.PolicyReq
@@ -1849,17 +1847,19 @@ func TestRetrieveDomain(t *testing.T) {
err: svcerr.ErrAuthentication,
},
{
desc: "retrieve domain with empty domainID",
token: accessToken,
domainID: "",
err: nil,
desc: "retrieve domain with empty domain id",
token: accessToken,
domainID: "",
err: svcerr.ErrViewEntity,
domainRepoErr1: repoerr.ErrNotFound,
},
{
desc: "retrieve non-existing domain",
token: accessToken,
domainID: inValid,
domainRepoErr: repoerr.ErrNotFound,
err: svcerr.ErrAuthorization,
desc: "retrieve non-existing domain",
token: accessToken,
domainID: inValid,
domainRepoErr: repoerr.ErrNotFound,
err: svcerr.ErrViewEntity,
domainRepoErr1: repoerr.ErrNotFound,
},
{
desc: "retrieve domain with failed to retrieve by id",
+66 -33
View File
@@ -33,6 +33,7 @@ import (
var (
id = generateUUID(&testing.T{})
validToken = "token"
adminToken = "adminToken"
validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22"
wrongID = testsutil.GenerateUUID(&testing.T{})
)
@@ -379,68 +380,100 @@ func TestClient(t *testing.T) {
Metadata: validMetadata,
Status: mgclients.EnabledStatus.String(),
}
basicUser := sdk.User{
Name: "clientname",
Status: mgclients.EnabledStatus.String(),
}
conf := sdk.Config{
UsersURL: ts.URL,
}
mgsdk := sdk.NewSDK(conf)
cases := []struct {
desc string
token string
clientID string
response sdk.User
err errors.SDKError
desc string
token string
clientID string
response sdk.User
retrieveByIDResponse sdk.User
err errors.SDKError
authorizeErr error
retrieveByIDErr error
checkSuperAdminErr errors.Error
identifyErr errors.Error
}{
{
desc: "view client successfully",
desc: "view client successfully",
response: basicUser,
token: validToken,
clientID: generateUUID(t),
authorizeErr: svcerr.ErrAuthentication,
checkSuperAdminErr: svcerr.ErrAuthentication,
err: nil,
},
{
desc: "view client successfully as admin",
response: user,
token: validToken,
token: adminToken,
clientID: generateUUID(t),
err: nil,
},
{
desc: "view client with an invalid token",
response: sdk.User{},
token: invalidToken,
clientID: generateUUID(t),
err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized),
desc: "view client with an invalid token",
response: sdk.User{},
token: invalidToken,
clientID: generateUUID(t),
identifyErr: svcerr.ErrAuthentication,
authorizeErr: svcerr.ErrAuthentication,
checkSuperAdminErr: svcerr.ErrAuthentication,
err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized),
retrieveByIDErr: svcerr.ErrAuthentication,
},
{
desc: "view client with valid token and invalid client id",
response: sdk.User{},
token: validToken,
clientID: wrongID,
err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest),
desc: "view client with valid token and invalid client id",
response: sdk.User{},
token: validToken,
clientID: wrongID,
authorizeErr: svcerr.ErrAuthentication,
checkSuperAdminErr: svcerr.ErrAuthentication,
err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest),
retrieveByIDErr: svcerr.ErrViewEntity,
},
{
desc: "view client with an invalid token and invalid client id",
response: sdk.User{},
token: invalidToken,
clientID: wrongID,
err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized),
desc: "view client with an invalid token and invalid client id",
response: sdk.User{},
token: invalidToken,
identifyErr: svcerr.ErrAuthentication,
authorizeErr: svcerr.ErrAuthentication,
clientID: wrongID,
checkSuperAdminErr: svcerr.ErrAuthentication,
err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized),
retrieveByIDErr: svcerr.ErrAuthentication,
},
{
desc: "view client as normal user with failed check on admin",
response: basicUser,
token: validToken,
authorizeErr: svcerr.ErrAuthentication,
checkSuperAdminErr: svcerr.ErrAuthentication,
clientID: generateUUID(t),
err: nil,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{UserId: validID}, nil)
if tc.token != validToken {
repoCall = auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{}, svcerr.ErrAuthentication)
}
repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil)
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{UserId: validID}, tc.identifyErr)
repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, tc.authorizeErr)
repoCall2 := crepo.On("RetrieveByID", mock.Anything, tc.clientID).Return(convertClient(tc.response), tc.err)
superAdminCall := crepo.On("CheckSuperAdmin", mock.Anything, mock.Anything).Return(tc.checkSuperAdminErr)
rClient, err := mgsdk.User(tc.clientID, tc.token)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
tc.response.Credentials.Secret = ""
assert.Equal(t, tc.response, rClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, rClient))
if tc.err == nil {
ok := repoCall.Parent.AssertCalled(t, "Identify", mock.Anything, mock.Anything)
assert.True(t, ok, fmt.Sprintf("Identify was not called on %s", tc.desc))
ok = repoCall2.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, tc.clientID)
assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc))
}
repoCall2.Unset()
repoCall1.Unset()
repoCall.Unset()
superAdminCall.Unset()
}
}
+16 -6
View File
@@ -148,16 +148,17 @@ func (svc service) ViewClient(ctx context.Context, token, id string) (mgclients.
return mgclients.Client{}, err
}
if tokenUserID != id {
if err := svc.checkSuperAdmin(ctx, tokenUserID); err != nil {
return mgclients.Client{}, err
}
}
client, err := svc.clients.RetrieveByID(ctx, id)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
if tokenUserID != id {
if err := svc.checkSuperAdmin(ctx, tokenUserID); err != nil {
return mgclients.Client{Name: client.Name, ID: client.ID}, nil
}
}
client.Credentials.Secret = ""
return client, nil
@@ -202,6 +203,11 @@ func (svc service) ListClients(ctx context.Context, token string, pm mgclients.P
if err != nil {
return mgclients.ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
for i, c := range pg.Clients {
pg.Clients[i] = mgclients.Client{ID: c.ID, Name: c.Name}
}
return pg, nil
}
@@ -498,6 +504,10 @@ func (svc service) ListMembers(ctx context.Context, token, objectKind, objectID
return mgclients.MembersPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
for i, c := range cp.Clients {
cp.Clients[i] = mgclients.Client{ID: c.ID, Name: c.Name}
}
if pm.ListPerms && len(cp.Clients) > 0 {
g, ctx := errgroup.WithContext(ctx)
+38 -26
View File
@@ -32,14 +32,19 @@ var (
phasher = hasher.New()
secret = "strongsecret"
validCMetadata = mgclients.Metadata{"role": "client"}
clientID = testsutil.GenerateUUID(&testing.T{})
client = mgclients.Client{
ID: testsutil.GenerateUUID(&testing.T{}),
ID: clientID,
Name: "clientname",
Tags: []string{"tag1", "tag2"},
Credentials: mgclients.Credentials{Identity: "clientidentity", Secret: secret},
Metadata: validCMetadata,
Status: mgclients.EnabledStatus,
}
basicClient = mgclients.Client{
Name: "clientname",
ID: clientID,
}
validToken = "token"
inValidToken = "invalid"
validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22"
@@ -387,13 +392,15 @@ func TestViewClient(t *testing.T) {
token: validToken,
clientID: client.ID,
err: nil,
checkSuperAdminErr: svcerr.ErrAuthorization,
},
{
desc: "view client with an invalid token",
identifyResponse: &magistrala.IdentityRes{},
response: mgclients.Client{},
token: inValidToken,
err: svcerr.ErrAuthentication,
desc: "view client with an invalid token",
identifyResponse: &magistrala.IdentityRes{},
response: mgclients.Client{},
token: inValidToken,
err: svcerr.ErrAuthentication,
checkSuperAdminErr: svcerr.ErrAuthorization,
},
{
desc: "view client as normal user with failed to retrieve client",
@@ -403,6 +410,7 @@ func TestViewClient(t *testing.T) {
clientID: client.ID,
retrieveByIDErr: repoerr.ErrNotFound,
err: svcerr.ErrNotFound,
checkSuperAdminErr: svcerr.ErrAuthorization,
},
{
desc: "view client as admin user successfully",
@@ -422,21 +430,25 @@ func TestViewClient(t *testing.T) {
err: svcerr.ErrAuthentication,
},
{
desc: "view client as admin user with invalid ID",
identifyResponse: &magistrala.IdentityRes{UserId: wrongID},
authorizeResponse: &magistrala.AuthorizeRes{Authorized: false},
token: validToken,
clientID: client.ID,
err: svcerr.ErrAuthorization,
},
{
desc: "view client as admin user with failed check on super admin",
identifyResponse: &magistrala.IdentityRes{UserId: adminID},
desc: "view client as admin user with invalid ID",
identifyResponse: &magistrala.IdentityRes{UserId: wrongID},
authorizeResponse: &magistrala.AuthorizeRes{Authorized: false},
token: validToken,
clientID: client.ID,
checkSuperAdminErr: svcerr.ErrAuthorization,
identifyErr: svcerr.ErrAuthorization,
err: svcerr.ErrAuthorization,
checkSuperAdminErr: nil,
},
{
desc: "view client as admin user with failed check on super admin",
identifyResponse: &magistrala.IdentityRes{UserId: adminID},
authorizeResponse: &magistrala.AuthorizeRes{Authorized: false},
token: validToken,
retrieveByIDResponse: basicClient,
response: basicClient,
clientID: client.ID,
checkSuperAdminErr: svcerr.ErrAuthorization,
err: nil,
},
}
@@ -562,7 +574,7 @@ func TestListClients(t *testing.T) {
Page: mgclients.Page{
Total: 1,
},
Clients: []mgclients.Client{client},
Clients: []mgclients.Client{basicClient},
},
token: validToken,
err: nil,
@@ -1627,7 +1639,7 @@ func TestListMembers(t *testing.T) {
svc, cRepo, auth, _ := newService(true)
validPolicy := fmt.Sprintf("%s_%s", validID, client.ID)
permissionsClient := client
permissionsClient := basicClient
permissionsClient.Permissions = []string{"read"}
cases := []struct {
@@ -1725,7 +1737,7 @@ func TestListMembers(t *testing.T) {
Offset: 0,
Limit: 100,
},
Members: []mgclients.Client{client},
Members: []mgclients.Client{basicClient},
},
err: nil,
},
@@ -1736,7 +1748,7 @@ func TestListMembers(t *testing.T) {
objectKind: authsvc.ThingsKind,
objectID: validID,
page: mgclients.Page{Offset: 0, Limit: 100, Permission: "read", ListPerms: true},
identifyResponse: &magistrala.IdentityRes{UserId: client.ID},
identifyResponse: &magistrala.IdentityRes{UserId: basicClient.ID},
authorizeReq: &magistrala.AuthorizeReq{
SubjectType: authsvc.UserType,
SubjectKind: authsvc.TokenKind,
@@ -1761,7 +1773,7 @@ func TestListMembers(t *testing.T) {
Offset: 0,
Limit: 100,
},
Clients: []mgclients.Client{client},
Clients: []mgclients.Client{basicClient},
},
listPermissionsResponse: &magistrala.ListPermissionsRes{Permissions: []string{"read"}},
response: mgclients.MembersPage{
@@ -1955,7 +1967,7 @@ func TestListMembers(t *testing.T) {
Offset: 0,
Limit: 100,
},
Clients: []mgclients.Client{client},
Clients: []mgclients.Client{basicClient},
},
response: mgclients.MembersPage{
Page: mgclients.Page{
@@ -1963,7 +1975,7 @@ func TestListMembers(t *testing.T) {
Offset: 0,
Limit: 100,
},
Members: []mgclients.Client{client},
Members: []mgclients.Client{basicClient},
},
err: nil,
},
@@ -2020,7 +2032,7 @@ func TestListMembers(t *testing.T) {
err: nil,
},
{
desc: "list members with policies successsfully of the domains kind",
desc: "list members with policies successsfully of the groups kind",
token: validToken,
groupID: validID,
objectKind: authsvc.GroupsKind,
@@ -2059,7 +2071,7 @@ func TestListMembers(t *testing.T) {
Offset: 0,
Limit: 100,
},
Members: []mgclients.Client{client},
Members: []mgclients.Client{basicClient},
},
err: nil,
},