diff --git a/auth/api/grpc/client.go b/auth/api/grpc/client.go index 7b30ba8c05..971721d45c 100644 --- a/auth/api/grpc/client.go +++ b/auth/api/grpc/client.go @@ -9,7 +9,6 @@ import ( "time" "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/go-kit/kit/endpoint" @@ -25,174 +24,7 @@ const ( policySvcName = "magistrala.PolicyService" ) -var ( - _ AuthServiceClient = (*authGrpcClient)(nil) - _ magistrala.PolicyServiceClient = (*policyGrpcClient)(nil) -) - -//go:generate mockery --name AuthServiceClient --output=../../mocks --filename auth_client.go --quiet --note "Copyright (c) Abstract Machines" -type AuthServiceClient interface { - magistrala.AuthzServiceClient - magistrala.AuthnServiceClient -} - -type authGrpcClient struct { - issue endpoint.Endpoint - refresh endpoint.Endpoint - identify endpoint.Endpoint - authorize endpoint.Endpoint - timeout time.Duration -} - -// NewAuthClient returns new auth gRPC client instance. -func NewAuthClient(conn *grpc.ClientConn, timeout time.Duration) AuthServiceClient { - return &authGrpcClient{ - issue: kitgrpc.NewClient( - conn, - authnSvcName, - "Issue", - encodeIssueRequest, - decodeIssueResponse, - magistrala.Token{}, - ).Endpoint(), - refresh: kitgrpc.NewClient( - conn, - authnSvcName, - "Refresh", - encodeRefreshRequest, - decodeRefreshResponse, - magistrala.Token{}, - ).Endpoint(), - identify: kitgrpc.NewClient( - conn, - authnSvcName, - "Identify", - encodeIdentifyRequest, - decodeIdentifyResponse, - magistrala.IdentityRes{}, - ).Endpoint(), - authorize: kitgrpc.NewClient( - conn, - authzSvcName, - "Authorize", - encodeAuthorizeRequest, - decodeAuthorizeResponse, - magistrala.AuthorizeRes{}, - ).Endpoint(), - timeout: timeout, - } -} - -func (client authGrpcClient) Issue(ctx context.Context, req *magistrala.IssueReq, _ ...grpc.CallOption) (*magistrala.Token, error) { - ctx, cancel := context.WithTimeout(ctx, client.timeout) - defer cancel() - - res, err := client.issue(ctx, issueReq{ - userID: req.GetUserId(), - domainID: req.GetDomainId(), - keyType: auth.KeyType(req.GetType()), - }) - if err != nil { - return &magistrala.Token{}, decodeError(err) - } - return res.(*magistrala.Token), nil -} - -func encodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(issueReq) - return &magistrala.IssueReq{ - UserId: req.userID, - DomainId: &req.domainID, - Type: uint32(req.keyType), - }, nil -} - -func decodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - return grpcRes, nil -} - -func (client authGrpcClient) Refresh(ctx context.Context, req *magistrala.RefreshReq, _ ...grpc.CallOption) (*magistrala.Token, error) { - ctx, cancel := context.WithTimeout(ctx, client.timeout) - defer cancel() - - res, err := client.refresh(ctx, refreshReq{refreshToken: req.GetRefreshToken(), domainID: req.GetDomainId()}) - if err != nil { - return &magistrala.Token{}, decodeError(err) - } - return res.(*magistrala.Token), nil -} - -func encodeRefreshRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(refreshReq) - return &magistrala.RefreshReq{RefreshToken: req.refreshToken, DomainId: &req.domainID}, nil -} - -func decodeRefreshResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - return grpcRes, nil -} - -func (client authGrpcClient) Identify(ctx context.Context, token *magistrala.IdentityReq, _ ...grpc.CallOption) (*magistrala.IdentityRes, error) { - ctx, cancel := context.WithTimeout(ctx, client.timeout) - defer cancel() - - res, err := client.identify(ctx, identityReq{token: token.GetToken()}) - if err != nil { - return &magistrala.IdentityRes{}, decodeError(err) - } - ir := res.(identityRes) - return &magistrala.IdentityRes{Id: ir.id, UserId: ir.userID, DomainId: ir.domainID}, nil -} - -func encodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(identityReq) - return &magistrala.IdentityReq{Token: req.token}, nil -} - -func decodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(*magistrala.IdentityRes) - return identityRes{id: res.GetId(), userID: res.GetUserId(), domainID: res.GetDomainId()}, nil -} - -func (client authGrpcClient) Authorize(ctx context.Context, req *magistrala.AuthorizeReq, _ ...grpc.CallOption) (r *magistrala.AuthorizeRes, err error) { - ctx, cancel := context.WithTimeout(ctx, client.timeout) - defer cancel() - - res, err := client.authorize(ctx, authReq{ - Domain: req.GetDomain(), - SubjectType: req.GetSubjectType(), - Subject: req.GetSubject(), - SubjectKind: req.GetSubjectKind(), - Relation: req.GetRelation(), - Permission: req.GetPermission(), - ObjectType: req.GetObjectType(), - Object: req.GetObject(), - }) - if err != nil { - return &magistrala.AuthorizeRes{}, decodeError(err) - } - - ar := res.(authorizeRes) - return &magistrala.AuthorizeRes{Authorized: ar.authorized, Id: ar.id}, nil -} - -func decodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(*magistrala.AuthorizeRes) - return authorizeRes{authorized: res.Authorized, id: res.Id}, nil -} - -func encodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(authReq) - return &magistrala.AuthorizeReq{ - Domain: req.Domain, - SubjectType: req.SubjectType, - Subject: req.Subject, - SubjectKind: req.SubjectKind, - Relation: req.Relation, - Permission: req.Permission, - ObjectType: req.ObjectType, - Object: req.Object, - }, nil -} +var _ magistrala.PolicyServiceClient = (*policyGrpcClient)(nil) type policyGrpcClient struct { deleteUserPolicies endpoint.Endpoint diff --git a/auth/api/grpc/endpoint_test.go b/auth/api/grpc/endpoint_test.go index ab5c2ed3c8..b1b288fa21 100644 --- a/auth/api/grpc/endpoint_test.go +++ b/auth/api/grpc/endpoint_test.go @@ -13,6 +13,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" grpcapi "github.com/absmach/magistrala/auth/api/grpc" + client "github.com/absmach/magistrala/internal/auth" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/errors" @@ -65,7 +66,7 @@ func startGRPCServer(svc auth.Service, port int) { func TestIssue(t *testing.T) { conn, err := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) - client := grpcapi.NewAuthClient(conn, time.Second) + client := client.NewAuthClient(conn, time.Second) cases := []struct { desc string @@ -134,7 +135,7 @@ func TestIssue(t *testing.T) { func TestRefresh(t *testing.T) { conn, err := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) - client := grpcapi.NewAuthClient(conn, time.Second) + client := client.NewAuthClient(conn, time.Second) cases := []struct { desc string @@ -180,7 +181,7 @@ func TestRefresh(t *testing.T) { func TestIdentify(t *testing.T) { conn, err := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) - client := grpcapi.NewAuthClient(conn, time.Second) + client := client.NewAuthClient(conn, time.Second) cases := []struct { desc string @@ -224,7 +225,7 @@ func TestIdentify(t *testing.T) { func TestAuthorize(t *testing.T) { conn, err := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) - client := grpcapi.NewAuthClient(conn, time.Second) + client := client.NewAuthClient(conn, time.Second) cases := []struct { desc string diff --git a/bootstrap/service.go b/bootstrap/service.go index e91306e3a9..77a25530af 100644 --- a/bootstrap/service.go +++ b/bootstrap/service.go @@ -12,7 +12,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" - grpcclient "github.com/absmach/magistrala/auth/api/grpc" + authclient "github.com/absmach/magistrala/pkg/auth" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" @@ -121,7 +121,7 @@ type ConfigReader interface { } type bootstrapService struct { - auth grpcclient.AuthServiceClient + auth authclient.AuthClient policy policy.PolicyClient configs ConfigRepository sdk mgsdk.SDK @@ -130,7 +130,7 @@ type bootstrapService struct { } // New returns new Bootstrap service. -func New(authClient grpcclient.AuthServiceClient, policyClient policy.PolicyClient, configs ConfigRepository, sdk mgsdk.SDK, encKey []byte, idp magistrala.IDProvider) Service { +func New(authClient authclient.AuthClient, policyClient policy.PolicyClient, configs ConfigRepository, sdk mgsdk.SDK, encKey []byte, idp magistrala.IDProvider) Service { return &bootstrapService{ configs: configs, sdk: sdk, diff --git a/cmd/bootstrap/main.go b/cmd/bootstrap/main.go index 0fc208ed03..21472605bd 100644 --- a/cmd/bootstrap/main.go +++ b/cmd/bootstrap/main.go @@ -14,7 +14,6 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" - authclient "github.com/absmach/magistrala/auth/api/grpc" "github.com/absmach/magistrala/bootstrap" "github.com/absmach/magistrala/bootstrap/api" "github.com/absmach/magistrala/bootstrap/events/consumer" @@ -23,6 +22,7 @@ import ( "github.com/absmach/magistrala/bootstrap/tracing" mgpolicy "github.com/absmach/magistrala/internal/policy" mglog "github.com/absmach/magistrala/logger" + authclient "github.com/absmach/magistrala/pkg/auth" "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/events/store" "github.com/absmach/magistrala/pkg/grpcclient" @@ -189,7 +189,7 @@ func main() { } } -func newService(ctx context.Context, authClient authclient.AuthServiceClient, policyClient policy.PolicyClient, db *sqlx.DB, tracer trace.Tracer, logger *slog.Logger, cfg config, dbConfig pgclient.Config) (bootstrap.Service, error) { +func newService(ctx context.Context, authClient authclient.AuthClient, policyClient policy.PolicyClient, db *sqlx.DB, tracer trace.Tracer, logger *slog.Logger, cfg config, dbConfig pgclient.Config) (bootstrap.Service, error) { database := pgclient.NewDatabase(db, dbConfig, tracer) repoConfig := bootstrappg.NewConfigRepository(database, logger) diff --git a/cmd/invitations/main.go b/cmd/invitations/main.go index 9bda59695f..423aca71f3 100644 --- a/cmd/invitations/main.go +++ b/cmd/invitations/main.go @@ -14,12 +14,12 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" - authclient "github.com/absmach/magistrala/auth/api/grpc" "github.com/absmach/magistrala/invitations" "github.com/absmach/magistrala/invitations/api" "github.com/absmach/magistrala/invitations/middleware" invitationspg "github.com/absmach/magistrala/invitations/postgres" mglog "github.com/absmach/magistrala/logger" + authclient "github.com/absmach/magistrala/pkg/auth" "github.com/absmach/magistrala/pkg/grpcclient" "github.com/absmach/magistrala/pkg/jaeger" "github.com/absmach/magistrala/pkg/postgres" @@ -155,7 +155,7 @@ func main() { } } -func newService(db *sqlx.DB, dbConfig clientspg.Config, authClient authclient.AuthServiceClient, tracer trace.Tracer, conf config, logger *slog.Logger) (invitations.Service, error) { +func newService(db *sqlx.DB, dbConfig clientspg.Config, authClient authclient.AuthClient, tracer trace.Tracer, conf config, logger *slog.Logger) (invitations.Service, error) { database := postgres.NewDatabase(db, dbConfig, tracer) repo := invitationspg.NewRepository(database) diff --git a/cmd/journal/main.go b/cmd/journal/main.go index 1fd342b9a2..cc61d4174d 100644 --- a/cmd/journal/main.go +++ b/cmd/journal/main.go @@ -14,13 +14,13 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" - authclient "github.com/absmach/magistrala/auth/api/grpc" "github.com/absmach/magistrala/journal" "github.com/absmach/magistrala/journal/api" "github.com/absmach/magistrala/journal/events" "github.com/absmach/magistrala/journal/middleware" journalpg "github.com/absmach/magistrala/journal/postgres" mglog "github.com/absmach/magistrala/logger" + authclient "github.com/absmach/magistrala/pkg/auth" "github.com/absmach/magistrala/pkg/events/store" "github.com/absmach/magistrala/pkg/grpcclient" jaegerclient "github.com/absmach/magistrala/pkg/jaeger" @@ -167,7 +167,7 @@ func main() { } } -func newService(db *sqlx.DB, dbConfig pgclient.Config, authClient authclient.AuthServiceClient, logger *slog.Logger, tracer trace.Tracer) journal.Service { +func newService(db *sqlx.DB, dbConfig pgclient.Config, authClient authclient.AuthClient, logger *slog.Logger, tracer trace.Tracer) journal.Service { database := postgres.NewDatabase(db, dbConfig, tracer) repo := journalpg.NewRepository(database) idp := uuid.New() diff --git a/cmd/things/main.go b/cmd/things/main.go index 84e3e9d3d7..670e4f243d 100644 --- a/cmd/things/main.go +++ b/cmd/things/main.go @@ -15,7 +15,6 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" - authclient "github.com/absmach/magistrala/auth/api/grpc" redisclient "github.com/absmach/magistrala/internal/clients/redis" mggroups "github.com/absmach/magistrala/internal/groups" gapi "github.com/absmach/magistrala/internal/groups/api" @@ -24,6 +23,7 @@ import ( gtracing "github.com/absmach/magistrala/internal/groups/tracing" mgpolicy "github.com/absmach/magistrala/internal/policy" mglog "github.com/absmach/magistrala/logger" + authclient "github.com/absmach/magistrala/pkg/auth" "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/grpcclient" jaegerclient "github.com/absmach/magistrala/pkg/jaeger" @@ -155,7 +155,7 @@ func main() { defer cacheclient.Close() var ( - authClient authclient.AuthServiceClient + authClient authclient.AuthClient policyClient policy.PolicyClient ) switch cfg.StandaloneID != "" && cfg.StandaloneToken != "" { @@ -241,7 +241,7 @@ func main() { } } -func newService(ctx context.Context, db *sqlx.DB, dbConfig pgclient.Config, authClient authclient.AuthServiceClient, policyClient policy.PolicyClient, cacheClient *redis.Client, keyDuration time.Duration, esURL string, tracer trace.Tracer, logger *slog.Logger) (things.Service, groups.Service, error) { +func newService(ctx context.Context, db *sqlx.DB, dbConfig pgclient.Config, authClient authclient.AuthClient, policyClient policy.PolicyClient, cacheClient *redis.Client, keyDuration time.Duration, esURL string, tracer trace.Tracer, logger *slog.Logger) (things.Service, groups.Service, error) { database := postgres.NewDatabase(db, dbConfig, tracer) cRepo := thingspg.NewRepository(database) gRepo := gpostgres.New(database) diff --git a/cmd/users/main.go b/cmd/users/main.go index 4b82f0f484..abd5f109ad 100644 --- a/cmd/users/main.go +++ b/cmd/users/main.go @@ -17,7 +17,6 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" authSvc "github.com/absmach/magistrala/auth" - authclient "github.com/absmach/magistrala/auth/api/grpc" "github.com/absmach/magistrala/internal/email" mggroups "github.com/absmach/magistrala/internal/groups" gapi "github.com/absmach/magistrala/internal/groups/api" @@ -26,6 +25,7 @@ import ( gtracing "github.com/absmach/magistrala/internal/groups/tracing" mgpolicy "github.com/absmach/magistrala/internal/policy" mglog "github.com/absmach/magistrala/logger" + authclient "github.com/absmach/magistrala/pkg/auth" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/grpcclient" @@ -235,7 +235,7 @@ func main() { } } -func newService(ctx context.Context, authClient authclient.AuthServiceClient, authPolicyClient magistrala.PolicyServiceClient, policyClient policy.PolicyClient, db *sqlx.DB, dbConfig pgclient.Config, tracer trace.Tracer, c config, ec email.Config, logger *slog.Logger) (users.Service, groups.Service, error) { +func newService(ctx context.Context, authClient authclient.AuthClient, authPolicyClient magistrala.PolicyServiceClient, policyClient policy.PolicyClient, db *sqlx.DB, dbConfig pgclient.Config, tracer trace.Tracer, c config, ec email.Config, logger *slog.Logger) (users.Service, groups.Service, error) { database := postgres.NewDatabase(db, dbConfig, tracer) cRepo := clientspg.NewRepository(database) gRepo := gpostgres.New(database) @@ -323,7 +323,7 @@ func createAdmin(ctx context.Context, c config, crepo clientspg.Repository, hsr return client.ID, nil } -func createAdminPolicy(ctx context.Context, clientID string, authClient authclient.AuthServiceClient, policyService policy.PolicyClient) error { +func createAdminPolicy(ctx context.Context, clientID string, authClient authclient.AuthClient, policyService policy.PolicyClient) error { res, err := authClient.Authorize(ctx, &magistrala.AuthorizeReq{ SubjectType: authSvc.UserType, Subject: clientID, diff --git a/internal/auth/client.go b/internal/auth/client.go new file mode 100644 index 0000000000..55be316dd2 --- /dev/null +++ b/internal/auth/client.go @@ -0,0 +1,211 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package auth + +import ( + "context" + "fmt" + "time" + + "github.com/absmach/magistrala" + mgauth "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/pkg/auth" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/go-kit/kit/endpoint" + kitgrpc "github.com/go-kit/kit/transport/grpc" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + authzSvcName = "magistrala.AuthzService" + authnSvcName = "magistrala.AuthnService" +) + +type authGrpcClient struct { + issue endpoint.Endpoint + refresh endpoint.Endpoint + identify endpoint.Endpoint + authorize endpoint.Endpoint + timeout time.Duration +} + +// NewAuthClient returns new auth gRPC client instance. +func NewAuthClient(conn *grpc.ClientConn, timeout time.Duration) auth.AuthClient { + return &authGrpcClient{ + issue: kitgrpc.NewClient( + conn, + authnSvcName, + "Issue", + encodeIssueRequest, + decodeIssueResponse, + magistrala.Token{}, + ).Endpoint(), + refresh: kitgrpc.NewClient( + conn, + authnSvcName, + "Refresh", + encodeRefreshRequest, + decodeRefreshResponse, + magistrala.Token{}, + ).Endpoint(), + identify: kitgrpc.NewClient( + conn, + authnSvcName, + "Identify", + encodeIdentifyRequest, + decodeIdentifyResponse, + magistrala.IdentityRes{}, + ).Endpoint(), + authorize: kitgrpc.NewClient( + conn, + authzSvcName, + "Authorize", + encodeAuthorizeRequest, + decodeAuthorizeResponse, + magistrala.AuthorizeRes{}, + ).Endpoint(), + timeout: timeout, + } +} + +func (client authGrpcClient) Issue(ctx context.Context, req *magistrala.IssueReq, _ ...grpc.CallOption) (*magistrala.Token, error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + res, err := client.issue(ctx, issueReq{ + userID: req.GetUserId(), + domainID: req.GetDomainId(), + keyType: mgauth.KeyType(req.GetType()), + }) + if err != nil { + return &magistrala.Token{}, decodeError(err) + } + return res.(*magistrala.Token), nil +} + +func encodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(issueReq) + return &magistrala.IssueReq{ + UserId: req.userID, + DomainId: &req.domainID, + Type: uint32(req.keyType), + }, nil +} + +func decodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + return grpcRes, nil +} + +func (client authGrpcClient) Refresh(ctx context.Context, req *magistrala.RefreshReq, _ ...grpc.CallOption) (*magistrala.Token, error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + res, err := client.refresh(ctx, refreshReq{refreshToken: req.GetRefreshToken(), domainID: req.GetDomainId()}) + if err != nil { + return &magistrala.Token{}, decodeError(err) + } + return res.(*magistrala.Token), nil +} + +func encodeRefreshRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(refreshReq) + return &magistrala.RefreshReq{RefreshToken: req.refreshToken, DomainId: &req.domainID}, nil +} + +func decodeRefreshResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + return grpcRes, nil +} + +func (client authGrpcClient) Identify(ctx context.Context, token *magistrala.IdentityReq, _ ...grpc.CallOption) (*magistrala.IdentityRes, error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + res, err := client.identify(ctx, identityReq{token: token.GetToken()}) + if err != nil { + return &magistrala.IdentityRes{}, decodeError(err) + } + ir := res.(identityRes) + return &magistrala.IdentityRes{Id: ir.id, UserId: ir.userID, DomainId: ir.domainID}, nil +} + +func encodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(identityReq) + return &magistrala.IdentityReq{Token: req.token}, nil +} + +func decodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(*magistrala.IdentityRes) + return identityRes{id: res.GetId(), userID: res.GetUserId(), domainID: res.GetDomainId()}, nil +} + +func (client authGrpcClient) Authorize(ctx context.Context, req *magistrala.AuthorizeReq, _ ...grpc.CallOption) (r *magistrala.AuthorizeRes, err error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + res, err := client.authorize(ctx, authReq{ + Domain: req.GetDomain(), + SubjectType: req.GetSubjectType(), + Subject: req.GetSubject(), + SubjectKind: req.GetSubjectKind(), + Relation: req.GetRelation(), + Permission: req.GetPermission(), + ObjectType: req.GetObjectType(), + Object: req.GetObject(), + }) + if err != nil { + return &magistrala.AuthorizeRes{}, decodeError(err) + } + + ar := res.(authorizeRes) + return &magistrala.AuthorizeRes{Authorized: ar.authorized, Id: ar.id}, nil +} + +func decodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(*magistrala.AuthorizeRes) + return authorizeRes{authorized: res.Authorized, id: res.Id}, nil +} + +func encodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(authReq) + return &magistrala.AuthorizeReq{ + Domain: req.Domain, + SubjectType: req.SubjectType, + Subject: req.Subject, + SubjectKind: req.SubjectKind, + Relation: req.Relation, + Permission: req.Permission, + ObjectType: req.ObjectType, + Object: req.Object, + }, nil +} + +func decodeError(err error) error { + if st, ok := status.FromError(err); ok { + switch st.Code() { + case codes.NotFound: + return errors.Wrap(svcerr.ErrNotFound, errors.New(st.Message())) + case codes.InvalidArgument: + return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) + case codes.AlreadyExists: + return errors.Wrap(svcerr.ErrConflict, errors.New(st.Message())) + case codes.Unauthenticated: + return errors.Wrap(svcerr.ErrAuthentication, errors.New(st.Message())) + case codes.OK: + if msg := st.Message(); msg != "" { + return errors.Wrap(errors.ErrUnidentified, errors.New(msg)) + } + return nil + case codes.FailedPrecondition: + return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) + case codes.PermissionDenied: + return errors.Wrap(svcerr.ErrAuthorization, errors.New(st.Message())) + default: + return errors.Wrap(fmt.Errorf("unexpected gRPC status: %s (status code:%v)", st.Code().String(), st.Code()), errors.New(st.Message())) + } + } + return err +} diff --git a/internal/auth/doc.go b/internal/auth/doc.go new file mode 100644 index 0000000000..3ee5ed3c93 --- /dev/null +++ b/internal/auth/doc.go @@ -0,0 +1,7 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package auth contains the implementation of the authorization and authentication +// gRPC client methods for magistrala services. + +package auth diff --git a/internal/auth/requests.go b/internal/auth/requests.go new file mode 100644 index 0000000000..7cfbc88889 --- /dev/null +++ b/internal/auth/requests.go @@ -0,0 +1,82 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package auth + +import ( + "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/pkg/apiutil" +) + +type identityReq struct { + token string +} + +func (req identityReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + + return nil +} + +type issueReq struct { + userID string + domainID string // optional + keyType auth.KeyType +} + +func (req issueReq) validate() error { + if req.keyType != auth.AccessKey && + req.keyType != auth.APIKey && + req.keyType != auth.RecoveryKey && + req.keyType != auth.InvitationKey { + return apiutil.ErrInvalidAuthKey + } + + return nil +} + +type refreshReq struct { + refreshToken string + domainID string // optional +} + +func (req refreshReq) validate() error { + if req.refreshToken == "" { + return apiutil.ErrMissingSecret + } + + return nil +} + +// authReq represents authorization request. It contains: +// 1. subject - an action invoker +// 2. object - an entity over which action will be executed +// 3. action - type of action that will be executed (read/write). +type authReq struct { + Domain string + SubjectType string + SubjectKind string + Subject string + Relation string + Permission string + ObjectType string + Object string +} + +func (req authReq) validate() error { + if req.Subject == "" || req.SubjectType == "" { + return apiutil.ErrMissingPolicySub + } + + if req.Object == "" || req.ObjectType == "" { + return apiutil.ErrMissingPolicyObj + } + + if req.Permission == "" { + return apiutil.ErrMalformedPolicyPer + } + + return nil +} diff --git a/internal/auth/responses.go b/internal/auth/responses.go new file mode 100644 index 0000000000..e49e2dbe96 --- /dev/null +++ b/internal/auth/responses.go @@ -0,0 +1,21 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package auth + +type identityRes struct { + id string + userID string + domainID string +} + +type issueRes struct { + accessToken string + refreshToken string + accessType string +} + +type authorizeRes struct { + id string + authorized bool +} diff --git a/internal/groups/service.go b/internal/groups/service.go index 512c567e92..015da96a69 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -10,8 +10,8 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" - grpcclient "github.com/absmach/magistrala/auth/api/grpc" "github.com/absmach/magistrala/pkg/apiutil" + authclient "github.com/absmach/magistrala/pkg/auth" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" @@ -28,13 +28,13 @@ var ( type service struct { groups groups.Repository - auth grpcclient.AuthServiceClient + auth authclient.AuthClient policy policy.PolicyClient idProvider magistrala.IDProvider } // NewService returns a new Clients service implementation. -func NewService(g groups.Repository, idp magistrala.IDProvider, authClient grpcclient.AuthServiceClient, policyClient policy.PolicyClient) groups.Service { +func NewService(g groups.Repository, idp magistrala.IDProvider, authClient authclient.AuthClient, policyClient policy.PolicyClient) groups.Service { return service{ groups: g, idProvider: idp, diff --git a/invitations/service.go b/invitations/service.go index 5c5b0060ed..ac80fb0218 100644 --- a/invitations/service.go +++ b/invitations/service.go @@ -9,7 +9,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" - grpcclient "github.com/absmach/magistrala/auth/api/grpc" + authclient "github.com/absmach/magistrala/pkg/auth" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" @@ -17,14 +17,14 @@ import ( type service struct { repo Repository - auth grpcclient.AuthServiceClient + auth authclient.AuthClient sdk mgsdk.SDK } // ErrMemberExist indicates that the user is already a member of the domain. var ErrMemberExist = errors.New("user is already a member of the domain") -func NewService(repo Repository, authClient grpcclient.AuthServiceClient, sdk mgsdk.SDK) Service { +func NewService(repo Repository, authClient authclient.AuthClient, sdk mgsdk.SDK) Service { return &service{ repo: repo, auth: authClient, diff --git a/journal/service.go b/journal/service.go index 0d2830c991..6839f12353 100644 --- a/journal/service.go +++ b/journal/service.go @@ -8,18 +8,18 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" - grpcclient "github.com/absmach/magistrala/auth/api/grpc" + authclient "github.com/absmach/magistrala/pkg/auth" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" ) type service struct { idProvider magistrala.IDProvider - auth grpcclient.AuthServiceClient + auth authclient.AuthClient repository Repository } -func NewService(idp magistrala.IDProvider, repository Repository, authClient grpcclient.AuthServiceClient) Service { +func NewService(idp magistrala.IDProvider, repository Repository, authClient authclient.AuthClient) Service { return &service{ idProvider: idp, auth: authClient, diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go new file mode 100644 index 0000000000..27c9524563 --- /dev/null +++ b/pkg/auth/auth.go @@ -0,0 +1,29 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package auth + +import ( + "context" + + "github.com/absmach/magistrala" + "google.golang.org/grpc" +) + +// AuthClient specifies a gRPC client for authentication and authorization for magistrala services. +type AuthClient interface { + // Issue issues a new Key, returning its token value alongside. + Issue(ctx context.Context, in *magistrala.IssueReq, opts ...grpc.CallOption) (*magistrala.Token, error) + + // Refresh iisues a refresh Key, returning its token value alongside. + Refresh(ctx context.Context, in *magistrala.RefreshReq, opts ...grpc.CallOption) (*magistrala.Token, error) + + // Identify validates token token. If token is valid, content + // is returned. If token is invalid, or invocation failed for some + // other reason, non-nil error value is returned in response. + Identify(ctx context.Context, in *magistrala.IdentityReq, opts ...grpc.CallOption) (*magistrala.IdentityRes, error) + + // Authorize checks if the `subject` is allowed to perform the `relation` on the `object`. + // Returns a non-nil error if the `subject` is not authorized. + Authorize(ctx context.Context, in *magistrala.AuthorizeReq, opts ...grpc.CallOption) (*magistrala.AuthorizeRes, error) +} diff --git a/pkg/auth/doc.go b/pkg/auth/doc.go new file mode 100644 index 0000000000..a8ad6abdfd --- /dev/null +++ b/pkg/auth/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package auth contains the auth concept definitions needed to support +// Magistrala authorization and authentication functionalities. +package auth diff --git a/pkg/grpcclient/client.go b/pkg/grpcclient/client.go index 699f7808dc..03da0ba635 100644 --- a/pkg/grpcclient/client.go +++ b/pkg/grpcclient/client.go @@ -8,6 +8,8 @@ import ( "github.com/absmach/magistrala" authgrpc "github.com/absmach/magistrala/auth/api/grpc" + authclient "github.com/absmach/magistrala/internal/auth" + "github.com/absmach/magistrala/pkg/auth" "github.com/absmach/magistrala/pkg/errors" thingsauth "github.com/absmach/magistrala/things/api/grpc" grpchealth "google.golang.org/grpc/health/grpc_health_v1" @@ -20,7 +22,7 @@ var errSvcNotServing = errors.New("service is not serving") // For example: // // authClient, authHandler, err := auth.SetupAuth(ctx, auth.Config{}). -func SetupAuthClient(ctx context.Context, cfg Config) (authgrpc.AuthServiceClient, Handler, error) { +func SetupAuthClient(ctx context.Context, cfg Config) (auth.AuthClient, Handler, error) { client, err := newHandler(cfg) if err != nil { return nil, nil, err @@ -34,7 +36,7 @@ func SetupAuthClient(ctx context.Context, cfg Config) (authgrpc.AuthServiceClien return nil, nil, errSvcNotServing } - return authgrpc.NewAuthClient(client.Connection(), cfg.Timeout), client, nil + return authclient.NewAuthClient(client.Connection(), cfg.Timeout), client, nil } // SetupPolicyClient loads Policy gRPC configuration and creates a new Policy gRPC client. diff --git a/things/service.go b/things/service.go index 8033c647c1..846ebb1133 100644 --- a/things/service.go +++ b/things/service.go @@ -8,7 +8,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" - grpcclient "github.com/absmach/magistrala/auth/api/grpc" + authclient "github.com/absmach/magistrala/pkg/auth" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" @@ -19,7 +19,7 @@ import ( ) type service struct { - auth grpcclient.AuthServiceClient + auth authclient.AuthClient policy policy.PolicyClient clients postgres.Repository clientCache Cache @@ -28,7 +28,7 @@ type service struct { } // NewService returns a new Clients service implementation. -func NewService(authClient grpcclient.AuthServiceClient, policyClient policy.PolicyClient, c postgres.Repository, grepo mggroups.Repository, tcache Cache, idp magistrala.IDProvider) Service { +func NewService(authClient authclient.AuthClient, policyClient policy.PolicyClient, c postgres.Repository, grepo mggroups.Repository, tcache Cache, idp magistrala.IDProvider) Service { return service{ auth: authClient, policy: policyClient, diff --git a/things/standalone/standalone.go b/things/standalone/standalone.go index 479749be1a..8709d43c4f 100644 --- a/things/standalone/standalone.go +++ b/things/standalone/standalone.go @@ -7,15 +7,15 @@ import ( "context" "github.com/absmach/magistrala" - grpcclient "github.com/absmach/magistrala/auth/api/grpc" + authclient "github.com/absmach/magistrala/pkg/auth" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/policy" "google.golang.org/grpc" ) var ( - _ grpcclient.AuthServiceClient = (*singleUserAuth)(nil) - _ policy.PolicyClient = (*singleUserPolicyClient)(nil) + _ authclient.AuthClient = (*singleUserAuth)(nil) + _ policy.PolicyClient = (*singleUserPolicyClient)(nil) ) type singleUserAuth struct { @@ -24,7 +24,7 @@ type singleUserAuth struct { } // NewAuthClient creates single user auth client for constrained environments. -func NewAuthClient(id, token string) grpcclient.AuthServiceClient { +func NewAuthClient(id, token string) authclient.AuthClient { return singleUserAuth{ id: id, token: token, diff --git a/users/service.go b/users/service.go index 93155dbb69..3131064b93 100644 --- a/users/service.go +++ b/users/service.go @@ -9,7 +9,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" - grpcclient "github.com/absmach/magistrala/auth/api/grpc" + authclient "github.com/absmach/magistrala/pkg/auth" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" @@ -29,7 +29,7 @@ var ( type service struct { clients postgres.Repository idProvider magistrala.IDProvider - auth grpcclient.AuthServiceClient + auth authclient.AuthClient policy policy.PolicyClient hasher Hasher email Emailer @@ -37,7 +37,7 @@ type service struct { } // NewService returns a new Users service implementation. -func NewService(crepo postgres.Repository, authClient grpcclient.AuthServiceClient, policyClient policy.PolicyClient, emailer Emailer, hasher Hasher, idp magistrala.IDProvider, selfRegister bool) Service { +func NewService(crepo postgres.Repository, authClient authclient.AuthClient, policyClient policy.PolicyClient, emailer Emailer, hasher Hasher, idp magistrala.IDProvider, selfRegister bool) Service { return service{ clients: crepo, auth: authClient,