-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #540 from kian99/CSS-9773-add-jaas-client
feat: add jaas client
- Loading branch information
Showing
11 changed files
with
407 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// Copyright 2024 Canonical Ltd. | ||
// Licensed under the Apache License, Version 2.0, see LICENCE file for details. | ||
|
||
package juju | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
|
||
"github.com/canonical/jimm-go-sdk/v3/api" | ||
"github.com/canonical/jimm-go-sdk/v3/api/params" | ||
jujuapi "github.com/juju/juju/api" | ||
) | ||
|
||
type jaasClient struct { | ||
SharedClient | ||
getJaasApiClient func(jujuapi.Connection) JaasAPIClient | ||
} | ||
|
||
func newJaasClient(sc SharedClient) *jaasClient { | ||
return &jaasClient{ | ||
SharedClient: sc, | ||
getJaasApiClient: func(conn jujuapi.Connection) JaasAPIClient { | ||
return api.NewClient(conn) | ||
}, | ||
} | ||
} | ||
|
||
// JaasTuple represents a tuple object of used by JAAS for permissions management. | ||
type JaasTuple struct { | ||
// Object represents the source side of the relation. | ||
Object string | ||
// Relation represents the level of access | ||
Relation string | ||
// Target represents the resource that you want `object` to have access to. | ||
Target string | ||
} | ||
|
||
func toAPITuples(tuples []JaasTuple) []params.RelationshipTuple { | ||
out := make([]params.RelationshipTuple, 0, len(tuples)) | ||
for _, tuple := range tuples { | ||
out = append(out, toAPITuple(tuple)) | ||
} | ||
return out | ||
} | ||
|
||
func toAPITuple(tuple JaasTuple) params.RelationshipTuple { | ||
return params.RelationshipTuple{ | ||
Object: tuple.Object, | ||
Relation: tuple.Relation, | ||
TargetObject: tuple.Target, | ||
} | ||
} | ||
|
||
// AddRelations attempts to create the provided slice of relationship tuples. | ||
// An empty slice of tuples will return an error. | ||
func (jc *jaasClient) AddRelations(tuples []JaasTuple) error { | ||
if len(tuples) == 0 { | ||
return errors.New("empty slice of tuples") | ||
} | ||
conn, err := jc.GetConnection(nil) | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { _ = conn.Close() }() | ||
cl := jc.getJaasApiClient(conn) | ||
req := params.AddRelationRequest{ | ||
Tuples: toAPITuples(tuples), | ||
} | ||
return cl.AddRelation(&req) | ||
} | ||
|
||
// DeleteRelations attempts to delete the provided slice of relationship tuples. | ||
// An empty slice of tuples will return an error. | ||
func (jc *jaasClient) DeleteRelations(tuples []JaasTuple) error { | ||
if len(tuples) == 0 { | ||
return errors.New("empty slice of tuples") | ||
} | ||
conn, err := jc.GetConnection(nil) | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { _ = conn.Close() }() | ||
cl := jc.getJaasApiClient(conn) | ||
req := params.RemoveRelationRequest{ | ||
Tuples: toAPITuples(tuples), | ||
} | ||
return cl.RemoveRelation(&req) | ||
} | ||
|
||
// ReadRelations attempts to read relations that match the criteria defined by `tuple`. | ||
// An nil tuple pointer is invalid and will return an error. | ||
func (jc *jaasClient) ReadRelations(ctx context.Context, tuple *JaasTuple) ([]params.RelationshipTuple, error) { | ||
if tuple == nil { | ||
return nil, errors.New("read relation tuple is nil") | ||
} | ||
|
||
conn, err := jc.GetConnection(nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer func() { _ = conn.Close() }() | ||
|
||
client := jc.getJaasApiClient(conn) | ||
relations := make([]params.RelationshipTuple, 0) | ||
req := ¶ms.ListRelationshipTuplesRequest{Tuple: toAPITuple(*tuple)} | ||
for { | ||
resp, err := client.ListRelationshipTuples(req) | ||
if err != nil { | ||
jc.Errorf(err, "call to ListRelationshipTuples failed") | ||
return nil, err | ||
} | ||
if len(resp.Errors) > 0 { | ||
jc.Errorf(err, "call to ListRelationshipTuples contained error(s)") | ||
return nil, errors.New(resp.Errors[0]) | ||
} | ||
relations = append(relations, resp.Tuples...) | ||
if resp.ContinuationToken == "" { | ||
return relations, nil | ||
} | ||
req.ContinuationToken = resp.ContinuationToken | ||
select { | ||
case <-ctx.Done(): | ||
return nil, ctx.Err() | ||
default: | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// Copyright 2024 Canonical Ltd. | ||
// Licensed under the Apache License, Version 2.0, see LICENCE file for details. | ||
|
||
package juju | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"testing" | ||
|
||
"github.com/canonical/jimm-go-sdk/v3/api/params" | ||
"github.com/juju/juju/api" | ||
"github.com/stretchr/testify/suite" | ||
"go.uber.org/mock/gomock" | ||
) | ||
|
||
type JaasSuite struct { | ||
suite.Suite | ||
JujuSuite | ||
|
||
mockJaasClient *MockJaasAPIClient | ||
} | ||
|
||
func (s *JaasSuite) setupMocks(t *testing.T) *gomock.Controller { | ||
ctlr := s.JujuSuite.setupMocks(t) | ||
s.mockJaasClient = NewMockJaasAPIClient(ctlr) | ||
|
||
return ctlr | ||
} | ||
|
||
func (s *JaasSuite) getJaasClient() jaasClient { | ||
return jaasClient{ | ||
SharedClient: s.JujuSuite.mockSharedClient, | ||
getJaasApiClient: func(connection api.Connection) JaasAPIClient { | ||
return s.mockJaasClient | ||
}, | ||
} | ||
} | ||
|
||
func (s *JaasSuite) TestAddRelations() { | ||
defer s.setupMocks(s.T()).Finish() | ||
|
||
tuples := []JaasTuple{ | ||
{Object: "object-1", Relation: "relation", Target: "target-1"}, | ||
{Object: "object-2", Relation: "relation", Target: "target-2"}, | ||
} | ||
req := params.AddRelationRequest{ | ||
Tuples: toAPITuples(tuples), | ||
} | ||
|
||
s.mockJaasClient.EXPECT().AddRelation( | ||
&req, | ||
).Return(nil) | ||
|
||
client := s.getJaasClient() | ||
err := client.AddRelations(tuples) | ||
s.Require().NoError(err) | ||
} | ||
|
||
func (s *JaasSuite) TestAddRelationsEmptySlice() { | ||
expectedErr := errors.New("empty slice of tuples") | ||
client := s.getJaasClient() | ||
err := client.AddRelations([]JaasTuple{}) | ||
s.Require().Error(err) | ||
s.Assert().Equal(expectedErr, err) | ||
} | ||
|
||
func (s *JaasSuite) TestDeleteRelations() { | ||
defer s.setupMocks(s.T()).Finish() | ||
|
||
tuples := []JaasTuple{ | ||
{Object: "object-1", Relation: "relation", Target: "target-1"}, | ||
{Object: "object-2", Relation: "relation", Target: "target-2"}, | ||
} | ||
req := params.RemoveRelationRequest{ | ||
Tuples: toAPITuples(tuples), | ||
} | ||
|
||
s.mockJaasClient.EXPECT().RemoveRelation( | ||
&req, | ||
).Return(nil) | ||
|
||
client := s.getJaasClient() | ||
err := client.DeleteRelations(tuples) | ||
s.Require().NoError(err) | ||
} | ||
|
||
func (s *JaasSuite) TestDeleteRelationsEmptySlice() { | ||
expectedErr := errors.New("empty slice of tuples") | ||
client := s.getJaasClient() | ||
err := client.DeleteRelations([]JaasTuple{}) | ||
s.Require().Error(err) | ||
s.Assert().Equal(expectedErr, err) | ||
} | ||
|
||
func (s *JaasSuite) TestReadRelations() { | ||
defer s.setupMocks(s.T()).Finish() | ||
|
||
tuple := JaasTuple{Object: "object-1", Relation: "relation", Target: "target-1"} | ||
// 1st request/response has no token in the request and a token in the response indicating another page is available. | ||
req := ¶ms.ListRelationshipTuplesRequest{Tuple: toAPITuple(tuple)} | ||
respWithToken := ¶ms.ListRelationshipTuplesResponse{ | ||
Tuples: []params.RelationshipTuple{toAPITuple(tuple)}, | ||
ContinuationToken: "token", | ||
} | ||
s.mockJaasClient.EXPECT().ListRelationshipTuples( | ||
req, | ||
).Return(respWithToken, nil) | ||
// 2nd request/response has the previous token in the request and no token in the response, indicating all pages have been consumed. | ||
reqWithToken := ¶ms.ListRelationshipTuplesRequest{Tuple: toAPITuple(tuple), ContinuationToken: "token"} | ||
respWithoutToken := ¶ms.ListRelationshipTuplesResponse{ | ||
Tuples: []params.RelationshipTuple{toAPITuple(tuple)}, | ||
ContinuationToken: "", | ||
} | ||
s.mockJaasClient.EXPECT().ListRelationshipTuples( | ||
reqWithToken, | ||
).Return(respWithoutToken, nil) | ||
|
||
client := s.getJaasClient() | ||
relations, err := client.ReadRelations(context.Background(), &tuple) | ||
s.Require().NoError(err) | ||
s.Require().Len(relations, 2) | ||
} | ||
|
||
func (s *JaasSuite) TestReadRelationsEmptyTuple() { | ||
expectedErr := errors.New("read relation tuple is nil") | ||
client := s.getJaasClient() | ||
_, err := client.ReadRelations(context.Background(), nil) | ||
s.Require().Error(err) | ||
s.Assert().Equal(expectedErr, err) | ||
} | ||
|
||
func (s *JaasSuite) TestReadRelationsCancelledContext() { | ||
defer s.setupMocks(s.T()).Finish() | ||
|
||
tuple := JaasTuple{Object: "object-1", Relation: "relation", Target: "target-1"} | ||
req := ¶ms.ListRelationshipTuplesRequest{Tuple: toAPITuple(tuple)} | ||
respWithToken := ¶ms.ListRelationshipTuplesResponse{ | ||
Tuples: []params.RelationshipTuple{toAPITuple(tuple)}, | ||
ContinuationToken: "token", | ||
} | ||
s.mockJaasClient.EXPECT().ListRelationshipTuples( | ||
req, | ||
).Return(respWithToken, nil) | ||
|
||
expectedErr := errors.New("context canceled") | ||
ctx := context.Background() | ||
ctx, cancelFunc := context.WithCancel(ctx) | ||
cancelFunc() | ||
|
||
client := s.getJaasClient() | ||
_, err := client.ReadRelations(ctx, &tuple) | ||
s.Require().Error(err) | ||
s.Assert().Equal(expectedErr, err) | ||
} | ||
|
||
// In order for 'go test' to run this suite, we need to create | ||
// a normal test function and pass our suite to suite.Run | ||
func TestJaasSuite(t *testing.T) { | ||
suite.Run(t, new(JaasSuite)) | ||
} |
Oops, something went wrong.