MG-2426 - Replace generic Clients in Users service (#2436)

Signed-off-by: Musilah <nataleigh.nk@gmail.com>
Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com>
Co-authored-by: Arvindh <arvindh91@gmail.com>
Co-authored-by: Felix Gateru <felix.gateru@gmail.com>
Co-authored-by: Dusan Borovcanin <borovcanindusan1@gmail.com>
This commit is contained in:
Nataly Musilah
2024-10-30 21:19:31 +03:00
committed by GitHub
parent 077882f672
commit 0019f71b46
75 changed files with 6231 additions and 4582 deletions
+228 -57
View File
@@ -76,8 +76,10 @@ paths:
- $ref: "#/components/parameters/Offset"
- $ref: "#/components/parameters/Metadata"
- $ref: "#/components/parameters/Status"
- $ref: "#/components/parameters/UserName"
- $ref: "#/components/parameters/UserIdentity"
- $ref: "#/components/parameters/FirstName"
- $ref: "#/components/parameters/LastName"
- $ref: "#/components/parameters/Username"
- $ref: "#/components/parameters/Email"
- $ref: "#/components/parameters/Tags"
security:
- bearerAuth: []
@@ -204,9 +206,44 @@ paths:
"500":
$ref: "#/components/responses/ServiceError"
/users/{userID}/username:
patch:
operationId: updateUsername
summary: Updates user's username.
description: |
Updates username of the user with provided ID. Username is
updated using authorization token and the new received username.
tags:
- Users
parameters:
- $ref: "#/components/parameters/UserID"
requestBody:
$ref: "#/components/requestBodies/UpdateUsernameReq"
security:
- bearerAuth: []
responses:
"200":
$ref: "#/components/responses/UserRes"
"400":
description: Failed due to malformed JSON.
"403":
description: Failed to perform authorization over the entity.
"404":
description: Failed due to non existing user.
"401":
description: Missing or invalid access token provided.
"409":
description: Failed due to using an existing username.
"415":
description: Missing or invalid content type.
"422":
description: Database can't process request.
"500":
$ref: "#/components/responses/ServiceError"
/users/{userID}/tags:
patch:
operationId: updateUserTags
operationId: updateTags
summary: Updates tags the user.
description: |
Updates tags of the user with provided ID. Tags is updated using
@@ -237,19 +274,52 @@ paths:
"500":
$ref: "#/components/responses/ServiceError"
/users/{userID}/identity:
/users/{userID}/picture:
patch:
operationId: updateUserIdentity
summary: Updates Identity of the user.
operationId: updateProfilePicture
summary: Updates the user's profile picture.
description: |
Updates identity of the user with provided ID. Identity is
updated using authorization token and the new received identity.
Updates the user's profile picture with provided ID. Profile picture is
updated using authorization token and the new received picture.
tags:
- Users
parameters:
- $ref: "#/components/parameters/UserID"
requestBody:
$ref: "#/components/requestBodies/UserUpdateIdentityReq"
$ref: "#/components/requestBodies/UserUpdateProfilePictureReq"
security:
- bearerAuth: []
responses:
"200":
$ref: "#/components/responses/UserRes"
"400":
description: Failed due to malformed JSON.
"403":
description: Failed to perform authorization over the entity.
"404":
description: Failed due to non existing user.
"401":
description: Missing or invalid access token provided.
"415":
description: Missing or invalid content type.
"422":
description: Database can't process request.
"500":
$ref: "#/components/responses/ServiceError"
/users/{userID}/email:
patch:
operationId: updateEmail
summary: Updates email of the user.
description: |
Updates email of the user with provided ID. Email is
updated using authorization token and the new received email.
tags:
- Users
parameters:
- $ref: "#/components/parameters/UserID"
requestBody:
$ref: "#/components/requestBodies/UserUpdateEmailReq"
security:
- bearerAuth: []
responses:
@@ -264,7 +334,7 @@ paths:
"401":
description: Missing or invalid access token provided.
"409":
description: Failed due to using an existing identity.
description: Failed due to using an existing email.
"415":
description: Missing or invalid content type.
"422":
@@ -274,7 +344,7 @@ paths:
/users/{userID}/role:
patch:
operationId: updateUserRole
operationId: updateRole
summary: Updates the user role.
description: |
Updates role for the user with provided ID.
@@ -370,7 +440,7 @@ paths:
/users/secret:
patch:
operationId: updateUserSecret
operationId: updateSecret
summary: Updates Secret of currently logged in user.
description: |
Updates secret of currently logged in user. Secret is updated using
@@ -408,8 +478,10 @@ paths:
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- $ref: "#/components/parameters/UserName"
- $ref: "#/components/parameters/UserIdentity"
- $ref: "#/components/parameters/Username"
- $ref: "#/components/parameters/FirstName"
- $ref: "#/components/parameters/LastName"
- $ref: "#/components/parameters/Email"
- $ref: "#/components/parameters/UserID"
security:
- bearerAuth: []
@@ -1092,10 +1164,18 @@ components:
UserReqObj:
type: object
properties:
name:
first_name:
type: string
example: userName
description: User name.
example: firstName
description: User's first name.
last_name:
type: string
example: lastName
description: User's last name.
email:
type: string
example: "admin@example.com"
description: User's email address will be used as its unique identifier.
tags:
type: array
minItems: 0
@@ -1106,10 +1186,10 @@ components:
credentials:
type: object
properties:
identity:
username:
type: string
example: "admin@example.com"
description: User's identity for example email address will be used as its unique identifier
example: "admin"
description: User's username for example 'admin' will be used as its unique identifier.
secret:
type: string
format: password
@@ -1120,6 +1200,10 @@ components:
type: object
example: { "domain": "example.com" }
description: Arbitrary, object-encoded user's data.
profile_picture:
type: string
example: "https://example.com/profile.jpg"
description: User's profile picture URL that is represented as a string.
status:
type: string
description: User Status
@@ -1163,10 +1247,14 @@ components:
format: uuid
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
description: User unique identifier.
name:
first_name:
type: string
example: userName
description: User name.
example: John
description: User's first name.
last_name:
type: string
example: Doe
description: User's last name.
tags:
type: array
minItems: 0
@@ -1174,17 +1262,25 @@ components:
type: string
example: ["tag1", "tag2"]
description: User tags.
email:
type: string
example: "john.doe@magistrala.com"
description: User email for example email address.
credentials:
type: object
properties:
identity:
username:
type: string
example: admin@magistrala.com
description: User Identity for example email address.
example: john_doe
description: User's username for example john_doe for Mr John Doe.
metadata:
type: object
example: { "address": "example" }
description: Arbitrary, object-encoded user's data.
profile_picture:
type: string
example: "https://example.com/profile.jpg"
description: User's profile picture URL that is represented as a string.
status:
type: string
description: User Status
@@ -1269,10 +1365,18 @@ components:
format: uuid
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
description: User unique identifier.
name:
first_name:
type: string
example: userName
description: User name.
example: John
description: User's first name.
last_name:
type: string
example: Doe
description: User's last name.
email:
type: string
example: user@magistrala.com
description: User's email address.
tags:
type: array
minItems: 0
@@ -1283,10 +1387,10 @@ components:
credentials:
type: object
properties:
identity:
username:
type: string
example: user@magistrala.com
description: User Identity for example email address.
example: john_doe
description: User's username.
secret:
type: string
example: password
@@ -1392,16 +1496,21 @@ components:
UserUpdate:
type: object
properties:
name:
first_name:
type: string
example: userName
description: User name.
example: firstName
description: User's first name.
last_name:
type: string
example: lastName
description: User's last name.
metadata:
type: object
example: { "role": "general" }
description: Arbitrary, object-encoded user's data.
required:
- name
- first_name
- last_name
- metadata
UserTags:
@@ -1416,15 +1525,25 @@ components:
items:
type: string
UserIdentity:
UserProfilePicture:
type: object
properties:
identity:
profile_picture:
type: string
example: "https://example.com/profile.jpg"
description: User's profile picture URL that is represented as a string.
required:
- profile_picture
Email:
type: object
properties:
email:
type: string
example: user@magistrala.com
description: User Identity for example email address.
description: User email address.
required:
- identity
- email
UserSecret:
type: object
@@ -1454,6 +1573,16 @@ components:
required:
- role
Username:
type: object
properties:
username:
type: string
example: "admin"
description: User's username for example 'admin' will be used as its unique identifier.
required:
- username
GroupUpdate:
type: object
properties:
@@ -1529,7 +1658,7 @@ components:
identity:
type: string
example: user@magistrala.com
description: User Identity for example email address.
description: User identity - email address.
secret:
type: string
example: password
@@ -1594,22 +1723,40 @@ components:
required: true
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
UserName:
name: name
description: User's name.
Username:
name: username
description: User's username.
in: query
schema:
type: string
required: false
example: "userName"
example: "username"
UserIdentity:
name: identity
description: User's identity.
FirstName:
name: first_name
description: User's first name.
in: query
schema:
type: string
pattern: "^[^\u0000-\u001F]*$"
required: false
example: "Jane"
LastName:
name: last_name
description: User's last name.
in: query
schema:
type: string
required: false
example: "Doe"
Email:
name: email
description: User's email address.
in: query
schema:
type: string
format: email
required: false
example: "admin@example.com"
@@ -1788,13 +1935,21 @@ components:
schema:
$ref: "#/components/schemas/UserTags"
UserUpdateIdentityReq:
description: Identity change data. User can change its identity.
UserUpdateProfilePictureReq:
description: JSON-formated document describing the profile picture of user to be update
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserIdentity"
$ref: "#/components/schemas/UserProfilePicture"
UserUpdateEmailReq:
description: Email change data. User can change its email.
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Email"
UserUpdateSecretReq:
description: Secret change data. User can change its secret.
@@ -1812,6 +1967,14 @@ components:
schema:
$ref: "#/components/schemas/UserRole"
UpdateUsernameReq:
description: JSON-formated document describing the username of the user to be updated
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Username"
GroupCreateReq:
description: JSON-formatted document describing the new group to be registered
required: true
@@ -1942,16 +2105,24 @@ components:
operationId: updateUser
parameters:
userID: $response.body#/id
update_tags:
operationId: updateUserTags
update_username:
operationId: updateUsername
parameters:
userID: $response.body#/id
update_identity:
operationId: updateUserIdentity
update_tags:
operationId: updateTags
parameters:
userID: $response.body#/id
update_profile_picture:
operationId: updateProfilePicture
parameters:
userID: $response.body#/id
update_email:
operationId: updateEmail
parameters:
userID: $response.body#/id
update_role:
operationId: updateUserRole
operationId: updateRole
parameters:
userID: $response.body#/id
disable:
+1 -1
View File
@@ -192,7 +192,7 @@ func TestAdd(t *testing.T) {
lastID := "0"
for _, tc := range cases {
tc.session = mgauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID}
sdkCall := tv.sdk.On("Thing", tc.config.ThingID, tc.domainID, tc.token).Return(mgsdk.Thing{ID: tc.config.ThingID, Credentials: mgsdk.Credentials{Secret: tc.config.ThingKey}}, errors.NewSDKError(tc.thingErr))
sdkCall := tv.sdk.On("Thing", tc.config.ThingID, tc.domainID, tc.token).Return(mgsdk.Thing{ID: tc.config.ThingID, Credentials: mgsdk.ClientCredentials{Secret: tc.config.ThingKey}}, errors.NewSDKError(tc.thingErr))
repoCall := tv.boot.On("ListExisting", context.Background(), domainID, mock.Anything).Return(tc.config.Channels, tc.listErr)
repoCall1 := tv.boot.On("Save", context.Background(), mock.Anything, mock.Anything).Return(mock.Anything, tc.saveErr)
+1 -1
View File
@@ -152,7 +152,7 @@ func TestAdd(t *testing.T) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
tc.session = mgauthn.Session{UserID: tc.userID, DomainID: tc.domainID, DomainUserID: validID}
repoCall := sdk.On("Thing", tc.config.ThingID, mock.Anything, tc.token).Return(mgsdk.Thing{ID: tc.config.ThingID, Credentials: mgsdk.Credentials{Secret: tc.config.ThingKey}}, tc.thingErr)
repoCall := sdk.On("Thing", tc.config.ThingID, mock.Anything, tc.token).Return(mgsdk.Thing{ID: tc.config.ThingID, Credentials: mgsdk.ClientCredentials{Secret: tc.config.ThingKey}}, tc.thingErr)
repoCall1 := sdk.On("CreateThing", mock.Anything, tc.domainID, tc.token).Return(mgsdk.Thing{}, tc.createThingErr)
repoCall2 := sdk.On("DeleteThing", tc.config.ThingID, tc.domainID, tc.token).Return(tc.deleteThingErr)
repoCall3 := boot.On("ListExisting", context.Background(), tc.domainID, mock.Anything).Return(tc.config.Channels, tc.listExistingErr)
+6 -6
View File
@@ -26,7 +26,7 @@ func New(svc bootstrap.Service, tracer trace.Tracer) bootstrap.Service {
// Add traces the "Add" operation of the wrapped bootstrap.Service.
func (tm *tracingMiddleware) Add(ctx context.Context, session mgauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) {
ctx, span := tm.tracer.Start(ctx, "svc_register_client", trace.WithAttributes(
ctx, span := tm.tracer.Start(ctx, "svc_register_user", trace.WithAttributes(
attribute.String("thing_id", cfg.ThingID),
attribute.String("domain_id ", cfg.DomainID),
attribute.String("name", cfg.Name),
@@ -41,7 +41,7 @@ func (tm *tracingMiddleware) Add(ctx context.Context, session mgauthn.Session, t
// View traces the "View" operation of the wrapped bootstrap.Service.
func (tm *tracingMiddleware) View(ctx context.Context, session mgauthn.Session, id string) (bootstrap.Config, error) {
ctx, span := tm.tracer.Start(ctx, "svc_view_client", trace.WithAttributes(
ctx, span := tm.tracer.Start(ctx, "svc_view_user", trace.WithAttributes(
attribute.String("id", id),
))
defer span.End()
@@ -51,7 +51,7 @@ func (tm *tracingMiddleware) View(ctx context.Context, session mgauthn.Session,
// Update traces the "Update" operation of the wrapped bootstrap.Service.
func (tm *tracingMiddleware) Update(ctx context.Context, session mgauthn.Session, cfg bootstrap.Config) error {
ctx, span := tm.tracer.Start(ctx, "svc_update_client", trace.WithAttributes(
ctx, span := tm.tracer.Start(ctx, "svc_update_user", trace.WithAttributes(
attribute.String("name", cfg.Name),
attribute.String("content", cfg.Content),
attribute.String("thing_id", cfg.ThingID),
@@ -85,7 +85,7 @@ func (tm *tracingMiddleware) UpdateConnections(ctx context.Context, session mgau
// List traces the "List" operation of the wrapped bootstrap.Service.
func (tm *tracingMiddleware) List(ctx context.Context, session mgauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) {
ctx, span := tm.tracer.Start(ctx, "svc_list_clients", trace.WithAttributes(
ctx, span := tm.tracer.Start(ctx, "svc_list_users", trace.WithAttributes(
attribute.Int64("offset", int64(offset)),
attribute.Int64("limit", int64(limit)),
))
@@ -96,7 +96,7 @@ func (tm *tracingMiddleware) List(ctx context.Context, session mgauthn.Session,
// Remove traces the "Remove" operation of the wrapped bootstrap.Service.
func (tm *tracingMiddleware) Remove(ctx context.Context, session mgauthn.Session, id string) error {
ctx, span := tm.tracer.Start(ctx, "svc_remove_client", trace.WithAttributes(
ctx, span := tm.tracer.Start(ctx, "svc_remove_user", trace.WithAttributes(
attribute.String("id", id),
))
defer span.End()
@@ -106,7 +106,7 @@ func (tm *tracingMiddleware) Remove(ctx context.Context, session mgauthn.Session
// Bootstrap traces the "Bootstrap" operation of the wrapped bootstrap.Service.
func (tm *tracingMiddleware) Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (bootstrap.Config, error) {
ctx, span := tm.tracer.Start(ctx, "svc_bootstrap_client", trace.WithAttributes(
ctx, span := tm.tracer.Start(ctx, "svc_bootstrap_user", trace.WithAttributes(
attribute.String("external_key", externalKey),
attribute.String("external_id", externalID),
attribute.Bool("secure", secure),
+2 -2
View File
@@ -106,7 +106,7 @@ func TestIssueCert(t *testing.T) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
sdkCall := sdk.On("Thing", tc.thingID, tc.domainID, tc.token).Return(mgsdk.Thing{ID: tc.thingID, Credentials: mgsdk.Credentials{Secret: thingKey}}, tc.thingErr)
sdkCall := sdk.On("Thing", tc.thingID, tc.domainID, tc.token).Return(mgsdk.Thing{ID: tc.thingID, Credentials: mgsdk.ClientCredentials{Secret: thingKey}}, tc.thingErr)
agentCall := agent.On("Issue", thingID, tc.ttl, tc.ipAddr).Return(tc.cert, tc.issueCertErr)
resp, err := svc.IssueCert(context.Background(), tc.domainID, tc.token, tc.thingID, tc.ttl)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
@@ -169,7 +169,7 @@ func TestRevokeCert(t *testing.T) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
sdkCall := sdk.On("Thing", tc.thingID, tc.domainID, tc.token).Return(mgsdk.Thing{ID: tc.thingID, Credentials: mgsdk.Credentials{Secret: thingKey}}, tc.thingErr)
sdkCall := sdk.On("Thing", tc.thingID, tc.domainID, tc.token).Return(mgsdk.Thing{ID: tc.thingID, Credentials: mgsdk.ClientCredentials{Secret: thingKey}}, tc.thingErr)
agentCall := agent.On("Revoke", mock.Anything).Return(tc.revokeErr)
agentCall1 := agent.On("ListCerts", mock.Anything).Return(tc.page, tc.listErr)
_, err := svc.RevokeCert(context.Background(), tc.domainID, tc.token, tc.thingID)
+5 -5
View File
@@ -135,9 +135,10 @@ var cmdProvision = []cobra.Command{
// Create test user
name := namesgenerator.Generate()
user := mgxsdk.User{
Name: name,
FirstName: name,
Email: fmt.Sprintf("%s@email.com", name),
Credentials: mgxsdk.Credentials{
Identity: fmt.Sprintf("%s@email.com", name),
Username: name,
Secret: "12345678",
},
Status: mgxsdk.EnabledStatus,
@@ -148,8 +149,7 @@ var cmdProvision = []cobra.Command{
return
}
user.Credentials.Secret = "12345678"
ut, err := sdk.CreateToken(mgxsdk.Login{Identity: user.Credentials.Identity, Secret: user.Credentials.Secret})
ut, err := sdk.CreateToken(mgxsdk.Login{Username: user.Username, Secret: user.Credentials.Secret})
if err != nil {
logErrorCmd(*cmd, err)
return
@@ -166,7 +166,7 @@ var cmdProvision = []cobra.Command{
return
}
ut, err = sdk.CreateToken(mgxsdk.Login{Identity: user.Credentials.Identity, Secret: user.Credentials.Secret})
ut, err = sdk.CreateToken(mgxsdk.Login{Email: user.Email, Secret: user.Credentials.Secret})
if err != nil {
logErrorCmd(*cmd, err)
return
+2 -2
View File
@@ -33,7 +33,7 @@ var (
var thing = sdk.Thing{
ID: testsutil.GenerateUUID(&testing.T{}),
Name: "testthing",
Credentials: sdk.Credentials{
Credentials: sdk.ClientCredentials{
Secret: "secret",
},
DomainID: testsutil.GenerateUUID(&testing.T{}),
@@ -385,7 +385,7 @@ func TestUpdateThingCmd(t *testing.T) {
ID: thing.ID,
DomainID: thing.DomainID,
Status: thing.Status,
Credentials: sdk.Credentials{
Credentials: sdk.ClientCredentials{
Secret: newSecret,
},
},
+41 -23
View File
@@ -9,36 +9,38 @@ import (
"net/url"
"strconv"
mgclients "github.com/absmach/magistrala/pkg/clients"
mgxsdk "github.com/absmach/magistrala/pkg/sdk/go"
"github.com/absmach/magistrala/users"
"github.com/spf13/cobra"
)
var cmdUsers = []cobra.Command{
{
Use: "create <name> <username> <password> <user_auth_token>",
Use: "create <first_name> <last_name> <email> <username> <password> <user_auth_token>",
Short: "Create user",
Long: "Create user with provided name, username and password. Token is optional\n" +
Long: "Create user with provided firstname, lastname, email, username and password. Token is optional\n" +
"For example:\n" +
"\tmagistrala-cli users create user user@example.com 12345678 $USER_AUTH_TOKEN\n",
"\tmagistrala-cli users create jane doe janedoe@example.com jane_doe 12345678 $USER_AUTH_TOKEN\n",
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 3 || len(args) > 4 {
if len(args) < 5 || len(args) > 6 {
logUsageCmd(*cmd, cmd.Use)
return
}
if len(args) == 3 {
if len(args) == 5 {
args = append(args, "")
}
user := mgxsdk.User{
Name: args[0],
FirstName: args[0],
LastName: args[1],
Email: args[2],
Credentials: mgxsdk.Credentials{
Identity: args[1],
Secret: args[2],
Username: args[3],
Secret: args[4],
},
Status: mgclients.EnabledStatus.String(),
Status: users.EnabledStatus.String(),
}
user, err := sdk.CreateUser(user, args[3])
user, err := sdk.CreateUser(user, args[5])
if err != nil {
logErrorCmd(*cmd, err)
return
@@ -66,6 +68,7 @@ var cmdUsers = []cobra.Command{
return
}
pageMetadata := mgxsdk.PageMetadata{
Username: Username,
Identity: Identity,
Offset: Offset,
Limit: Limit,
@@ -93,21 +96,21 @@ var cmdUsers = []cobra.Command{
{
Use: "token <username> <password>",
Short: "Get token",
Long: "Generate new token from username and password\n" +
Long: "Generate a new token with username and password\n" +
"For example:\n" +
"\tmagistrala-cli users token user@example.com 12345678\n",
"\tmagistrala-cli users token jane.doe 12345678\n",
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
logUsageCmd(*cmd, cmd.Use)
return
}
lg := mgxsdk.Login{
Identity: args[0],
loginReq := mgxsdk.Login{
Username: args[0],
Secret: args[1],
}
token, err := sdk.CreateToken(lg)
token, err := sdk.CreateToken(loginReq)
if err != nil {
logErrorCmd(*cmd, err)
return
@@ -116,6 +119,7 @@ var cmdUsers = []cobra.Command{
logJSONCmd(*cmd, token)
},
},
{
Use: "refreshtoken <token>",
Short: "Get token",
@@ -138,13 +142,15 @@ var cmdUsers = []cobra.Command{
},
},
{
Use: "update [<user_id> <JSON_string> | tags <user_id> <tags> | identity <user_id> <identity> ] <user_auth_token>",
Use: "update [<user_id> <JSON_string> | tags <user_id> <tags> | username <user_id> <username> | email <user_id> <email>] <user_auth_token>",
Short: "Update user",
Long: "Updates either user name and metadata or user tags or user identity\n" +
Long: "Updates either user name and metadata or user tags or user email\n" +
"Usage:\n" +
"\tmagistrala-cli users update <user_id> '{\"name\":\"new name\", \"metadata\":{\"key\": \"value\"}}' $USERTOKEN - updates user name and metadata\n" +
"\tmagistrala-cli users update <user_id> '{\"first_name\":\"new first_name\", \"metadata\":{\"key\": \"value\"}}' $USERTOKEN - updates user first and lastname and metadata\n" +
"\tmagistrala-cli users update tags <user_id> '[\"tag1\", \"tag2\"]' $USERTOKEN - updates user tags\n" +
"\tmagistrala-cli users update identity <user_id> newidentity@example.com $USERTOKEN - updates user identity\n",
"\tmagistrala-cli users update username <user_id> newusername $USERTOKEN - updates user name\n" +
"\tmagistrala-cli users update email <user_id> newemail@example.com $USERTOKEN - updates user email\n",
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 4 && len(args) != 3 {
logUsageCmd(*cmd, cmd.Use)
@@ -168,10 +174,22 @@ var cmdUsers = []cobra.Command{
return
}
if args[0] == "identity" {
if args[0] == "email" {
user.ID = args[1]
user.Credentials.Identity = args[2]
user, err := sdk.UpdateUserIdentity(user, args[3])
user.Email = args[2]
user, err := sdk.UpdateUserEmail(user, args[3])
if err != nil {
logErrorCmd(*cmd, err)
return
}
logJSONCmd(*cmd, user)
return
}
if args[0] == "username" {
user.ID = args[1]
user.Credentials.Username = args[2]
user, err := sdk.UpdateUser(user, args[3])
if err != nil {
logErrorCmd(*cmd, err)
return
+41 -32
View File
@@ -22,11 +22,12 @@ import (
)
var user = mgsdk.User{
ID: testsutil.GenerateUUID(&testing.T{}),
Name: "testuser",
ID: testsutil.GenerateUUID(&testing.T{}),
FirstName: "testuserfirstname",
LastName: "testuserfirstname",
Credentials: mgsdk.Credentials{
Secret: "testpassword",
Identity: "identity@example.com",
Username: "testusername",
},
Status: mgclients.EnabledStatus.String(),
}
@@ -57,9 +58,11 @@ func TestCreateUsersCmd(t *testing.T) {
{
desc: "create user successfully with token",
args: []string{
user.Name,
user.Credentials.Identity,
user.FirstName,
user.LastName,
user.Email,
user.Credentials.Secret,
user.Credentials.Username,
validToken,
},
user: user,
@@ -68,9 +71,11 @@ func TestCreateUsersCmd(t *testing.T) {
{
desc: "create user successfully without token",
args: []string{
user.Name,
user.Credentials.Identity,
user.FirstName,
user.LastName,
user.Email,
user.Credentials.Secret,
user.Credentials.Username,
},
user: user,
logType: entityLog,
@@ -78,9 +83,12 @@ func TestCreateUsersCmd(t *testing.T) {
{
desc: "failed to create user",
args: []string{
user.Name,
user.Credentials.Identity,
user.FirstName,
user.LastName,
user.Email,
user.Credentials.Secret,
user.Credentials.Username,
validToken,
},
sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity).Error()),
@@ -88,7 +96,7 @@ func TestCreateUsersCmd(t *testing.T) {
},
{
desc: "create user with invalid args",
args: []string{user.Name, user.Credentials.Identity},
args: []string{user.FirstName, user.Credentials.Username},
logType: usageLog,
},
}
@@ -96,12 +104,13 @@ func TestCreateUsersCmd(t *testing.T) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
sdkCall := sdkMock.On("CreateUser", mock.Anything, mock.Anything).Return(tc.user, tc.sdkerr)
if len(tc.args) == 3 {
if len(tc.args) == 4 {
sdkUser := mgsdk.User{
Name: tc.args[0],
FirstName: tc.args[0],
LastName: tc.args[1],
Email: tc.args[2],
Credentials: mgsdk.Credentials{
Identity: tc.args[1],
Secret: tc.args[2],
Secret: tc.args[3],
},
}
sdkCall = sdkMock.On("CreateUser", mock.Anything, sdkUser).Return(tc.user, tc.sdkerr)
@@ -297,7 +306,7 @@ func TestIssueTokenCmd(t *testing.T) {
{
desc: "issue token successfully",
args: []string{
user.Credentials.Identity,
user.Email,
user.Credentials.Secret,
},
sdkerr: nil,
@@ -307,7 +316,7 @@ func TestIssueTokenCmd(t *testing.T) {
{
desc: "issue token with failed authentication",
args: []string{
user.Credentials.Identity,
user.Email,
invalidPassword,
},
sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
@@ -318,7 +327,7 @@ func TestIssueTokenCmd(t *testing.T) {
{
desc: "issue token with invalid args",
args: []string{
user.Credentials.Identity,
user.Email,
user.Credentials.Secret,
extraArg,
},
@@ -329,8 +338,8 @@ func TestIssueTokenCmd(t *testing.T) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
lg := mgsdk.Login{
Identity: tc.args[0],
Secret: tc.args[1],
Email: tc.args[0],
Secret: tc.args[1],
}
sdkCall := sdkMock.On("CreateToken", lg).Return(tc.token, tc.sdkerr)
@@ -391,7 +400,7 @@ func TestRefreshIssueTokenCmd(t *testing.T) {
logType: usageLog,
},
{
desc: "issue refresh token with invalid identity",
desc: "issue refresh token with invalid Username",
args: []string{
"invalidToken",
},
@@ -435,9 +444,9 @@ func TestUpdateUserCmd(t *testing.T) {
userID := testsutil.GenerateUUID(t)
tagUpdateType := "tags"
identityUpdateType := "identity"
emailUpdateType := "email"
roleUpdateType := "role"
newIdentity := "newidentity@example.com"
newEmail := "newemail@example.com"
newRole := "administrator"
newTagsJSON := "[\"tag1\", \"tag2\"]"
newNameMetadataJSON := "{\"name\":\"new name\", \"metadata\":{\"key\": \"value\"}}"
@@ -487,22 +496,22 @@ func TestUpdateUserCmd(t *testing.T) {
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
},
{
desc: "update user identity successfully",
desc: "update user email successfully",
args: []string{
identityUpdateType,
emailUpdateType,
userID,
newIdentity,
newEmail,
validToken,
},
logType: entityLog,
user: user,
},
{
desc: "update user identity with invalid token",
desc: "update user email with invalid token",
args: []string{
identityUpdateType,
emailUpdateType,
userID,
newIdentity,
newEmail,
invalidToken,
},
logType: errLog,
@@ -590,19 +599,19 @@ func TestUpdateUserCmd(t *testing.T) {
u.ID = tc.args[1]
sdkCall1 = sdkMock.On("UpdateUserTags", u, tc.args[3]).Return(tc.user, tc.sdkerr)
case tc.args[0] == identityUpdateType:
case tc.args[0] == emailUpdateType:
var u mgsdk.User
u.Credentials.Identity = tc.args[2]
u.Email = tc.args[2]
u.ID = tc.args[1]
sdkCall2 = sdkMock.On("UpdateUserIdentity", u, tc.args[3]).Return(tc.user, tc.sdkerr)
sdkCall2 = sdkMock.On("UpdateUserEmail", u, tc.args[3]).Return(tc.user, tc.sdkerr)
case tc.args[0] == roleUpdateType && len(tc.args) == 4:
sdkCall3 = sdkMock.On("UpdateUserRole", mgsdk.User{
Role: tc.args[2],
}, tc.args[3]).Return(tc.user, tc.sdkerr)
case tc.args[0] == userID:
sdkCall = sdkMock.On("UpdateUser", mgsdk.User{
Name: "new name",
FirstName: "new name",
Metadata: mgsdk.Metadata{
"key": "value",
},
+6
View File
@@ -36,6 +36,12 @@ var (
Contact string = ""
// RawOutput raw output mode.
RawOutput bool = false
// Username query parameter.
Username string = ""
// FirstName query parameter.
FirstName string = ""
// LastName query parameter.
LastName string = ""
)
func logJSONCmd(cmd cobra.Command, iList ...interface{}) {
+25 -20
View File
@@ -26,7 +26,6 @@ import (
authsvcAuthn "github.com/absmach/magistrala/pkg/authn/authsvc"
mgauthz "github.com/absmach/magistrala/pkg/authz"
authsvcAuthz "github.com/absmach/magistrala/pkg/authz/authsvc"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/pkg/groups"
"github.com/absmach/magistrala/pkg/grpcclient"
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
@@ -75,6 +74,9 @@ type config struct {
LogLevel string `env:"MG_USERS_LOG_LEVEL" envDefault:"info"`
AdminEmail string `env:"MG_USERS_ADMIN_EMAIL" envDefault:"admin@example.com"`
AdminPassword string `env:"MG_USERS_ADMIN_PASSWORD" envDefault:"12345678"`
AdminUsername string `env:"MG_USERS_ADMIN_USERNAME" envDefault:"admin"`
AdminFirstName string `env:"MG_USERS_ADMIN_FIRST_NAME" envDefault:"super"`
AdminLastName string `env:"MG_USERS_ADMIN_LAST_NAME" envDefault:"admin"`
PassRegexText string `env:"MG_USERS_PASS_REGEX" envDefault:"^.{8,}$"`
ResetURL string `env:"MG_TOKEN_RESET_ENDPOINT" envDefault:"/reset-request"`
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
@@ -256,6 +258,7 @@ func main() {
func newService(ctx context.Context, authz mgauthz.Authorization, token magistrala.TokenServiceClient, policyService policies.Service, domainsClient magistrala.DomainsServiceClient, 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)
@@ -292,11 +295,11 @@ func newService(ctx context.Context, authz mgauthz.Authorization, token magistra
counter, latency = prometheus.MakeMetrics("groups", "api")
gsvc = gmiddleware.MetricsMiddleware(gsvc, counter, latency)
clientID, err := createAdmin(ctx, c, cRepo, hsr, csvc)
userID, err := createAdmin(ctx, c, cRepo, hsr, csvc)
if err != nil {
logger.Error(fmt.Sprintf("failed to create admin client: %s", err))
}
if err := createAdminPolicy(ctx, clientID, authz, policyService); err != nil {
if err := createAdminPolicy(ctx, userID, authz, policyService); err != nil {
return nil, nil, err
}
@@ -305,7 +308,7 @@ func newService(ctx context.Context, authz mgauthz.Authorization, token magistra
return csvc, gsvc, err
}
func createAdmin(ctx context.Context, c config, crepo clientspg.Repository, hsr users.Hasher, svc users.Service) (string, error) {
func createAdmin(ctx context.Context, c config, urepo users.Repository, hsr users.Hasher, svc users.Service) (string, error) {
id, err := uuid.New().ID()
if err != nil {
return "", err
@@ -315,47 +318,49 @@ func createAdmin(ctx context.Context, c config, crepo clientspg.Repository, hsr
return "", err
}
client := mgclients.Client{
ID: id,
Name: "admin",
Credentials: mgclients.Credentials{
Identity: c.AdminEmail,
user := users.User{
ID: id,
Email: c.AdminEmail,
FirstName: c.AdminFirstName,
LastName: c.AdminLastName,
Credentials: users.Credentials{
Username: "admin",
Secret: hash,
},
Metadata: mgclients.Metadata{
Metadata: users.Metadata{
"role": "admin",
},
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Role: mgclients.AdminRole,
Status: mgclients.EnabledStatus,
Role: users.AdminRole,
Status: users.EnabledStatus,
}
if c, err := crepo.RetrieveByIdentity(ctx, client.Credentials.Identity); err == nil {
return c.ID, nil
if u, err := urepo.RetrieveByEmail(ctx, user.Email); err == nil {
return u.ID, nil
}
// Create an admin
if _, err = crepo.Save(ctx, client); err != nil {
if _, err = urepo.Save(ctx, user); err != nil {
return "", err
}
if _, err = svc.IssueToken(ctx, c.AdminEmail, c.AdminPassword); err != nil {
if _, err = svc.IssueToken(ctx, c.AdminUsername, c.AdminPassword); err != nil {
return "", err
}
return client.ID, nil
return user.ID, nil
}
func createAdminPolicy(ctx context.Context, clientID string, authz mgauthz.Authorization, policyService policies.Service) error {
func createAdminPolicy(ctx context.Context, userID string, authz mgauthz.Authorization, policyService policies.Service) error {
if err := authz.Authorize(ctx, mgauthz.PolicyReq{
SubjectType: policies.UserType,
Subject: clientID,
Subject: userID,
Permission: policies.AdministratorRelation,
Object: policies.MagistralaObject,
ObjectType: policies.PlatformType,
}); err != nil {
err := policyService.AddPolicy(ctx, policies.Policy{
SubjectType: policies.UserType,
Subject: clientID,
Subject: userID,
Relation: policies.AdministratorRelation,
Object: policies.MagistralaObject,
ObjectType: policies.PlatformType,
+4
View File
@@ -171,6 +171,9 @@ MG_USERS_LOG_LEVEL=debug
MG_USERS_SECRET_KEY=HyE2D4RUt9nnKG6v8zKEqAp6g6ka8hhZsqUpzgKvnwpXrNVQSH
MG_USERS_ADMIN_EMAIL=admin@example.com
MG_USERS_ADMIN_PASSWORD=12345678
MG_USERS_ADMIN_USERNAME=admin
MG_USERS_ADMIN_FIRST_NAME=super
MG_USERS_ADMIN_LAST_NAME=admin
MG_USERS_PASS_REGEX=^.{8,}$
MG_USERS_ACCESS_TOKEN_DURATION=15m
MG_USERS_REFRESH_TOKEN_DURATION=24h
@@ -310,6 +313,7 @@ MG_PROVISION_SERVER_KEY=
MG_PROVISION_USERS_LOCATION=http://users:9002
MG_PROVISION_THINGS_LOCATION=http://things:9000
MG_PROVISION_USER=
MG_PROVISION_USERNAME=
MG_PROVISION_PASS=
MG_PROVISION_API_KEY=
MG_PROVISION_CERTS_SVC_URL=http://certs:9019
@@ -28,6 +28,7 @@ services:
MG_PROVISION_USERS_LOCATION: ${MG_PROVISION_USERS_LOCATION}
MG_PROVISION_THINGS_LOCATION: ${MG_PROVISION_THINGS_LOCATION}
MG_PROVISION_USER: ${MG_PROVISION_USER}
MG_PROVISION_USERNAME: ${MG_PROVISION_USERNAME}
MG_PROVISION_PASS: ${MG_PROVISION_PASS}
MG_PROVISION_API_KEY: ${MG_PROVISION_API_KEY}
MG_PROVISION_CERTS_SVC_URL: ${MG_PROVISION_CERTS_SVC_URL}
+3
View File
@@ -412,6 +412,9 @@ services:
MG_USERS_SECRET_KEY: ${MG_USERS_SECRET_KEY}
MG_USERS_ADMIN_EMAIL: ${MG_USERS_ADMIN_EMAIL}
MG_USERS_ADMIN_PASSWORD: ${MG_USERS_ADMIN_PASSWORD}
MG_USERS_ADMIN_USERNAME: ${MG_USERS_ADMIN_USERNAME}
MG_USERS_ADMIN_FIRST_NAME: ${MG_USERS_ADMIN_FIRST_NAME}
MG_USERS_ADMIN_LAST_NAME: ${MG_USERS_ADMIN_LAST_NAME}
MG_USERS_PASS_REGEX: ${MG_USERS_PASS_REGEX}
MG_USERS_ACCESS_TOKEN_DURATION: ${MG_USERS_ACCESS_TOKEN_DURATION}
MG_USERS_REFRESH_TOKEN_DURATION: ${MG_USERS_REFRESH_TOKEN_DURATION}
+1 -1
View File
@@ -55,7 +55,7 @@ require (
golang.org/x/oauth2 v0.23.0
golang.org/x/sync v0.8.0
gonum.org/v1/gonum v0.15.1
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53
google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.35.1
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
+2 -2
View File
@@ -612,8 +612,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+11 -4
View File
@@ -30,11 +30,13 @@ const (
ParentKey = "parent_id"
OwnerKey = "owner_id"
ClientKey = "client"
IdentityKey = "identity"
UsernameKey = "username"
NameKey = "name"
GroupKey = "group"
ActionKey = "action"
TagKey = "tag"
NameKey = "name"
FirstNameKey = "first_name"
LastNameKey = "last_name"
TotalKey = "total"
SubjectKey = "subject"
ObjectKey = "object"
@@ -43,6 +45,7 @@ const (
DirKey = "dir"
ListPerms = "list_perms"
VisibilityKey = "visibility"
EmailKey = "email"
SharedByKey = "shared_by"
TokenKey = "token"
DefPermission = "view"
@@ -127,6 +130,7 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) {
errors.Contains(err, apiutil.ErrMissingName),
errors.Contains(err, apiutil.ErrMissingAlias),
errors.Contains(err, apiutil.ErrMissingEmail),
errors.Contains(err, apiutil.ErrInvalidEmail),
errors.Contains(err, apiutil.ErrMissingHost),
errors.Contains(err, apiutil.ErrInvalidResetPass),
errors.Contains(err, apiutil.ErrEmptyList),
@@ -140,7 +144,6 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) {
errors.Contains(err, apiutil.ErrInvalidQueryParams),
errors.Contains(err, apiutil.ErrMissingRelation),
errors.Contains(err, apiutil.ErrValidation),
errors.Contains(err, apiutil.ErrMissingIdentity),
errors.Contains(err, apiutil.ErrMissingPass),
errors.Contains(err, apiutil.ErrMissingConfPass),
errors.Contains(err, apiutil.ErrPasswordFormat),
@@ -165,7 +168,11 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) {
errors.Contains(err, apiutil.ErrEmptySearchQuery),
errors.Contains(err, apiutil.ErrLenSearchQuery),
errors.Contains(err, apiutil.ErrMissingDomainID),
errors.Contains(err, certs.ErrFailedReadFromPKI):
errors.Contains(err, certs.ErrFailedReadFromPKI),
errors.Contains(err, apiutil.ErrMissingUsername),
errors.Contains(err, apiutil.ErrMissingFirstName),
errors.Contains(err, apiutil.ErrMissingLastName),
errors.Contains(err, apiutil.ErrInvalidUsername):
err = unwrap(err)
w.WriteHeader(http.StatusBadRequest)
+18
View File
@@ -87,6 +87,9 @@ var (
// ErrMissingEmail indicates missing email.
ErrMissingEmail = errors.New("missing email")
// ErrInvalidEmail indicates missing email.
ErrInvalidEmail = errors.New("invalid email")
// ErrMissingHost indicates missing host.
ErrMissingHost = errors.New("missing host")
@@ -188,4 +191,19 @@ var (
// ErrMissingDomainID indicates missing domainID.
ErrMissingDomainID = errors.New("missing domainID")
// ErrMissingUsername indicates missing user name.
ErrMissingUsername = errors.New("missing username")
// ErrInvalidUsername indicates missing user name.
ErrInvalidUsername = errors.New("invalid username")
// ErrMissingFirstName indicates missing first name.
ErrMissingFirstName = errors.New("missing first name")
// ErrMissingLastName indicates missing last name.
ErrMissingLastName = errors.New("missing last name")
// ErrInvalidProfilePictureURL indicates that the profile picture url is invalid.
ErrInvalidProfilePictureURL = errors.New("invalid profile picture url")
)
-1
View File
@@ -49,7 +49,6 @@ type Client struct {
UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
Status Status `json:"status,omitempty"` // 1 for enabled, 0 for disabled
Role Role `json:"role,omitempty"` // 1 for admin, 0 for normal user
Permissions []string `json:"permissions,omitempty"`
}
+3
View File
@@ -21,4 +21,7 @@ type Page struct {
Identity string `json:"identity,omitempty"`
Role Role `json:"-"`
ListPerms bool `json:"-"`
Username string `json:"username,omitempty"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
}
-7
View File
@@ -392,7 +392,6 @@ func ToDBClient(c clients.Client) (DBClient, error) {
UpdatedAt: updatedAt,
UpdatedBy: updatedBy,
Status: c.Status,
Role: &c.Role,
}, nil
}
@@ -431,9 +430,6 @@ func ToClient(c DBClient) (clients.Client, error) {
UpdatedBy: updatedBy,
Status: c.Status,
}
if c.Role != nil {
cli.Role = *c.Role
}
return cli, nil
}
@@ -491,9 +487,6 @@ func PageQuery(pm clients.Page) (string, error) {
if pm.Tag != "" {
query = append(query, "EXISTS (SELECT 1 FROM unnest(tags) AS tag WHERE tag ILIKE '%' || :tag || '%')")
}
if pm.Role != clients.AllRole {
query = append(query, "c.role = :role")
}
// If there are search params presents, use search and ignore other options.
// Always combine role with search params, so len(query) > 1.
if len(query) > 1 {
+32 -165
View File
@@ -13,9 +13,7 @@ import (
"github.com/0x6flab/namegenerator"
"github.com/absmach/magistrala/internal/testsutil"
"github.com/absmach/magistrala/pkg/clients"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/pkg/clients/postgres"
pgclients "github.com/absmach/magistrala/pkg/clients/postgres"
"github.com/absmach/magistrala/pkg/errors"
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
@@ -35,9 +33,9 @@ func TestRetrieveByID(t *testing.T) {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
repo := &pgclients.Repository{database}
client := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo)
client := generateClient(t, mgclients.EnabledStatus, repo)
cases := []struct {
desc string
@@ -85,9 +83,9 @@ func TestRetrieveByIdentity(t *testing.T) {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
repo := &pgclients.Repository{database}
client := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo)
client := generateClient(t, mgclients.EnabledStatus, repo)
cases := []struct {
desc string
@@ -135,7 +133,7 @@ func TestRetrieveAll(t *testing.T) {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
repo := &pgclients.Repository{database}
nClients := uint64(200)
@@ -156,11 +154,9 @@ func TestRetrieveAll(t *testing.T) {
},
Status: mgclients.EnabledStatus,
CreatedAt: time.Now().UTC().Truncate(time.Millisecond),
Role: mgclients.UserRole,
}
if i%50 == 0 {
client.Status = mgclients.DisabledStatus
client.Role = mgclients.AdminRole
}
client, err := save(context.Background(), repo, client)
require.Nil(t, err, fmt.Sprintf("add new client: expected nil got %s\n", err))
@@ -523,73 +519,6 @@ func TestRetrieveAll(t *testing.T) {
Clients: []mgclients.Client(nil),
},
},
{
desc: "with user role",
pm: mgclients.Page{
Offset: 0,
Limit: 10,
Role: mgclients.UserRole,
Status: mgclients.AllStatus,
},
response: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 196,
Offset: 0,
Limit: 10,
},
Clients: expectedClients[1:11],
},
},
{
desc: "with admin role",
pm: mgclients.Page{
Offset: 0,
Limit: nClients,
Role: mgclients.AdminRole,
Status: mgclients.AllStatus,
},
response: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 4,
Offset: 0,
Limit: nClients,
},
Clients: disabledClients,
},
},
{
desc: "with combined role",
pm: mgclients.Page{
Offset: 0,
Limit: nClients,
Status: mgclients.AllStatus,
Role: mgclients.AllRole,
},
response: mgclients.ClientsPage{
Page: mgclients.Page{
Total: nClients,
Offset: 0,
Limit: nClients,
},
Clients: expectedClients,
},
},
{
desc: "with the wrong role",
pm: mgclients.Page{
Offset: 0,
Limit: nClients,
Role: 10,
},
response: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 0,
Offset: 0,
Limit: nClients,
},
Clients: []mgclients.Client(nil),
},
},
{
desc: "with tag",
pm: mgclients.Page{
@@ -668,7 +597,7 @@ func TestRetrieveByIDs(t *testing.T) {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
repo := &pgclients.Repository{database}
num := 200
@@ -681,12 +610,12 @@ func TestRetrieveByIDs(t *testing.T) {
Name: name,
Credentials: mgclients.Credentials{
Identity: name + emailSuffix,
Secret: password,
Secret: testsutil.GenerateUUID(t),
},
Tags: namegen.GenerateMultiple(5),
Metadata: map[string]interface{}{"name": name},
CreatedAt: time.Now().UTC().Truncate(time.Millisecond),
Status: clients.EnabledStatus,
Status: mgclients.EnabledStatus,
}
client, err := save(context.Background(), repo, client)
require.Nil(t, err, fmt.Sprintf("add new client: expected nil got %s\n", err))
@@ -932,7 +861,7 @@ func TestSearchClients(t *testing.T) {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
repo := &pgclients.Repository{database}
name := namegen.Generate()
@@ -945,7 +874,7 @@ func TestSearchClients(t *testing.T) {
Name: username,
Credentials: mgclients.Credentials{
Identity: username,
Secret: password,
Secret: fmt.Sprintf("%s%d", password, i),
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
@@ -1311,10 +1240,10 @@ func TestUpdate(t *testing.T) {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
repo := &pgclients.Repository{database}
client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo)
client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo)
client1 := generateClient(t, mgclients.EnabledStatus, repo)
client2 := generateClient(t, mgclients.DisabledStatus, repo)
cases := []struct {
desc string
@@ -1487,10 +1416,10 @@ func TestUpdateTags(t *testing.T) {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
repo := &pgclients.Repository{database}
client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo)
client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo)
client1 := generateClient(t, mgclients.EnabledStatus, repo)
client2 := generateClient(t, mgclients.DisabledStatus, repo)
cases := []struct {
desc string
@@ -1547,10 +1476,10 @@ func TestUpdateSecret(t *testing.T) {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
repo := &pgclients.Repository{database}
client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo)
client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo)
client1 := generateClient(t, mgclients.EnabledStatus, repo)
client2 := generateClient(t, mgclients.DisabledStatus, repo)
cases := []struct {
desc string
@@ -1615,10 +1544,10 @@ func TestUpdateIdentity(t *testing.T) {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
repo := &pgclients.Repository{database}
client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo)
client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo)
client1 := generateClient(t, mgclients.EnabledStatus, repo)
client2 := generateClient(t, mgclients.DisabledStatus, repo)
cases := []struct {
desc string
@@ -1681,10 +1610,10 @@ func TestChangeStatus(t *testing.T) {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
repo := &pgclients.Repository{database}
client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo)
client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo)
client1 := generateClient(t, mgclients.EnabledStatus, repo)
client2 := generateClient(t, mgclients.DisabledStatus, repo)
cases := []struct {
desc string
@@ -1737,75 +1666,14 @@ func TestChangeStatus(t *testing.T) {
}
}
func TestUpdateRole(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
client1 := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo)
client2 := generateClient(t, mgclients.DisabledStatus, mgclients.UserRole, repo)
cases := []struct {
desc string
client mgclients.Client
err error
}{
{
desc: "for an enabled client",
client: mgclients.Client{
ID: client1.ID,
Role: mgclients.AdminRole,
},
err: nil,
},
{
desc: "for a disabled client",
client: mgclients.Client{
ID: client2.ID,
Role: mgclients.AdminRole,
},
err: repoerr.ErrNotFound,
},
{
desc: "for invalid client",
client: mgclients.Client{
ID: testsutil.GenerateUUID(t),
Role: mgclients.AdminRole,
},
err: repoerr.ErrNotFound,
},
{
desc: "for empty client",
client: mgclients.Client{},
err: repoerr.ErrNotFound,
},
}
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
c.client.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond)
c.client.UpdatedBy = testsutil.GenerateUUID(t)
expected, err := repo.UpdateRole(context.Background(), c.client)
assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err))
if err == nil {
assert.Equal(t, c.client.Role, expected.Role)
assert.Equal(t, c.client.UpdatedAt, expected.UpdatedAt)
assert.Equal(t, c.client.UpdatedBy, expected.UpdatedBy)
}
})
}
}
func TestDelete(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := &postgres.Repository{database}
repo := &pgclients.Repository{database}
client := generateClient(t, mgclients.EnabledStatus, mgclients.UserRole, repo)
client := generateClient(t, mgclients.EnabledStatus, repo)
cases := []struct {
desc string
@@ -1854,20 +1722,19 @@ func findClients(clis []mgclients.Client, query string, offset, limit uint64) []
return rclients[offset:limit]
}
func generateClient(t *testing.T, status mgclients.Status, role mgclients.Role, repo *postgres.Repository) mgclients.Client {
func generateClient(t *testing.T, status mgclients.Status, repo *pgclients.Repository) mgclients.Client {
client := mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: namegen.Generate(),
Credentials: mgclients.Credentials{
Identity: namegen.Generate() + emailSuffix,
Secret: password,
Secret: testsutil.GenerateUUID(t),
},
Tags: namegen.GenerateMultiple(5),
Metadata: mgclients.Metadata{
"name": namegen.Generate(),
},
Status: status,
Role: role,
CreatedAt: time.Now().UTC().Truncate(time.Millisecond),
}
_, err := save(context.Background(), repo, client)
@@ -1876,9 +1743,9 @@ func generateClient(t *testing.T, status mgclients.Status, role mgclients.Role,
return client
}
func save(ctx context.Context, repo *postgres.Repository, c mgclients.Client) (mgclients.Client, error) {
q := `INSERT INTO clients (id, name, tags, domain_id, identity, secret, metadata, created_at, status, role)
VALUES (:id, :name, :tags, :domain_id, :identity, :secret, :metadata, :created_at, :status, :role)
func save(ctx context.Context, repo *pgclients.Repository, c mgclients.Client) (mgclients.Client, error) {
q := `INSERT INTO clients (id, name, tags, domain_id, identity, secret, metadata, created_at, status)
VALUES (:id, :name, :tags, :domain_id, :identity, :secret, :metadata, :created_at, :status)
RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at`
dbc, err := pgclients.ToDBClient(c)
if err != nil {
+2 -2
View File
@@ -13,7 +13,7 @@ import (
"github.com/absmach/magistrala/pkg/postgres"
pgClient "github.com/absmach/magistrala/pkg/postgres"
upostgres "github.com/absmach/magistrala/users/postgres"
tpostgres "github.com/absmach/magistrala/things/postgres"
"github.com/jmoiron/sqlx"
dockertest "github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
@@ -76,7 +76,7 @@ func TestMain(m *testing.M) {
SSLRootCert: "",
}
if db, err = pgClient.Setup(dbConfig, *upostgres.Migration()); err != nil {
if db, err = pgClient.Setup(dbConfig, *tpostgres.Migration()); err != nil {
log.Fatalf("Could not setup test DB connection: %s", err)
}
+3
View File
@@ -33,4 +33,7 @@ var (
// ErrFailedToRetrieveAllGroups failed to retrieve groups.
ErrFailedToRetrieveAllGroups = errors.New("failed to retrieve all groups")
// ErrMissingNames indicates missing first and last names.
ErrMissingNames = errors.New("missing first or last name")
)
+3
View File
@@ -72,4 +72,7 @@ var (
// ErrParentGroupAuthorization indicates failure occurred while authorizing the parent group.
ErrParentGroupAuthorization = errors.New("failed to authorize parent group")
// ErrMissingUsername indicates that the user's names are missing.
ErrMissingUsername = errors.New("missing usernames")
)
+20 -19
View File
@@ -11,9 +11,9 @@ import (
"net/url"
"time"
mfclients "github.com/absmach/magistrala/pkg/clients"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
mgoauth2 "github.com/absmach/magistrala/pkg/oauth2"
uclient "github.com/absmach/magistrala/users"
"golang.org/x/oauth2"
googleoauth2 "golang.org/x/oauth2/google"
)
@@ -84,47 +84,48 @@ func (cfg *config) Exchange(ctx context.Context, code string) (oauth2.Token, err
return *token, nil
}
func (cfg *config) UserInfo(accessToken string) (mfclients.Client, error) {
func (cfg *config) UserInfo(accessToken string) (uclient.User, error) {
resp, err := http.Get(userInfoURL + url.QueryEscape(accessToken))
if err != nil {
return mfclients.Client{}, err
return uclient.User{}, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return mfclients.Client{}, svcerr.ErrAuthentication
return uclient.User{}, svcerr.ErrAuthentication
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return mfclients.Client{}, err
return uclient.User{}, err
}
var user struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Picture string `json:"picture"`
ID string `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Username string `json:"username"`
Email string `json:"email"`
Picture string `json:"picture"`
}
if err := json.Unmarshal(data, &user); err != nil {
return mfclients.Client{}, err
return uclient.User{}, err
}
if user.ID == "" || user.Name == "" || user.Email == "" {
return mfclients.Client{}, svcerr.ErrAuthentication
if user.ID == "" || user.FirstName == "" || user.LastName == "" || user.Email == "" {
return uclient.User{}, svcerr.ErrAuthentication
}
client := mfclients.Client{
ID: user.ID,
Name: user.Name,
Credentials: mfclients.Credentials{
Identity: user.Email,
},
client := uclient.User{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email,
Metadata: map[string]interface{}{
"oauth_provider": providerName,
"profile_picture": user.Picture,
},
Status: mfclients.EnabledStatus,
Status: uclient.EnabledStatus,
}
return client, nil
+7 -7
View File
@@ -7,10 +7,10 @@ package mocks
import (
context "context"
clients "github.com/absmach/magistrala/pkg/clients"
mock "github.com/stretchr/testify/mock"
users "github.com/absmach/magistrala/users"
xoauth2 "golang.org/x/oauth2"
)
@@ -138,22 +138,22 @@ func (_m *Provider) State() string {
}
// UserInfo provides a mock function with given fields: accessToken
func (_m *Provider) UserInfo(accessToken string) (clients.Client, error) {
func (_m *Provider) UserInfo(accessToken string) (users.User, error) {
ret := _m.Called(accessToken)
if len(ret) == 0 {
panic("no return value specified for UserInfo")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(string) (clients.Client, error)); ok {
if rf, ok := ret.Get(0).(func(string) (users.User, error)); ok {
return rf(accessToken)
}
if rf, ok := ret.Get(0).(func(string) clients.Client); ok {
if rf, ok := ret.Get(0).(func(string) users.User); ok {
r0 = rf(accessToken)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(string) error); ok {
+2 -2
View File
@@ -6,7 +6,7 @@ package oauth2
import (
"context"
mfclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/users"
"golang.org/x/oauth2"
)
@@ -42,5 +42,5 @@ type Provider interface {
Exchange(ctx context.Context, code string) (oauth2.Token, error)
// UserInfo retrieves the user's information using the access token.
UserInfo(accessToken string) (mfclients.Client, error)
UserInfo(accessToken string) (users.User, error)
}
+12 -7
View File
@@ -3,8 +3,8 @@
package sdk
// updateClientSecretReq is used to update the client secret.
type updateClientSecretReq struct {
// updateUserSecretReq is used to update the user secret.
type updateUserSecretReq struct {
OldSecret string `json:"old_secret,omitempty"`
NewSecret string `json:"new_secret,omitempty"`
}
@@ -24,11 +24,11 @@ type updateThingSecretReq struct {
Secret string `json:"secret,omitempty"`
}
// updateClientIdentityReq is used to update the client identity.
type updateClientIdentityReq struct {
token string
id string
Identity string `json:"identity,omitempty"`
// updateUserEmailReq is used to update the user email.
type updateUserEmailReq struct {
token string
id string
Email string `json:"email,omitempty"`
}
// UserPasswordReq contains old and new passwords.
@@ -51,3 +51,8 @@ type UsersRelationRequest struct {
type UserGroupsRequest struct {
UserGroupIDs []string `json:"group_ids"`
}
type UpdateUsernameReq struct {
id string
Username string `json:"username"`
}
+50 -9
View File
@@ -95,6 +95,10 @@ type PageMetadata struct {
Direction string `json:"direction,omitempty"`
Level uint64 `json:"level,omitempty"`
Identity string `json:"identity,omitempty"`
Email string `json:"email,omitempty"`
Username string `json:"username,omitempty"`
LastName string `json:"last_name,omitempty"`
FirstName string `json:"first_name,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
@@ -125,10 +129,10 @@ type PageMetadata struct {
}
// Credentials represent client credentials: it contains
// "identity" which can be a username, email, generated name;
// "username" which can be a username, generated name;
// and "secret" which can be a password or access token.
type Credentials struct {
Identity string `json:"identity,omitempty"` // username or generated login ID
Username string `json:"username,omitempty"` // username or generated login ID
Secret string `json:"secret,omitempty"` // password or token
}
@@ -141,8 +145,9 @@ type SDK interface {
// example:
// user := sdk.User{
// Name: "John Doe",
// Email: "john.doe@example",
// Credentials: sdk.Credentials{
// Identity: "john.doe@example",
// Username: "john.doe",
// Secret: "12345678",
// },
// }
@@ -202,6 +207,19 @@ type SDK interface {
// fmt.Println(user)
UpdateUser(user User, token string) (User, errors.SDKError)
// UpdateUserEmail updates the user's email
//
// example:
// user := sdk.User{
// ID: "userID",
// Credentials: sdk.Credentials{
// Email: "john.doe@example",
// },
// }
// user, _ := sdk.UpdateUserEmail(user, "token")
// fmt.Println(user)
UpdateUserEmail(user User, token string) (User, errors.SDKError)
// UpdateUserTags updates the user's tags.
//
// example:
@@ -213,18 +231,29 @@ type SDK interface {
// fmt.Println(user)
UpdateUserTags(user User, token string) (User, errors.SDKError)
// UpdateUserIdentity updates the user's identity
// UpdateUsername updates the user's Username.
//
// example:
// user := sdk.User{
// ID: "userID",
// Credentials: sdk.Credentials{
// Identity: "john.doe@example",
// },
// Username: "john.doe",
// },
// }
// user, _ := sdk.UpdateUserIdentity(user, "token")
// user, _ := sdk.UpdateUsername(user, "token")
// fmt.Println(user)
UpdateUserIdentity(user User, token string) (User, errors.SDKError)
UpdateUsername(user User, token string) (User, errors.SDKError)
// UpdateProfilePicture updates the user's profile picture.
//
// example:
// user := sdk.User{
// ID: "userID",
// ProfilePicture: "https://cloudstorage.example.com/bucket-name/user-images/profile-picture.jpg",
// }
// user, _ := sdk.UpdateProfilePicture(user, "token")
// fmt.Println(user)
UpdateProfilePicture(user User, token string) (User, errors.SDKError)
// UpdateUserRole updates the user's role.
//
@@ -283,7 +312,7 @@ type SDK interface {
//
// example:
// lt := sdk.Login{
// Identity: "john.doe@example",
// Email: "john.doe@example",
// Secret: "12345678",
// }
// token, _ := sdk.CreateToken(lt)
@@ -1326,9 +1355,21 @@ func (pm PageMetadata) query() (string, error) {
if pm.Level != 0 {
q.Add("level", strconv.FormatUint(pm.Level, 10))
}
if pm.Email != "" {
q.Add("email", pm.Email)
}
if pm.Identity != "" {
q.Add("identity", pm.Identity)
}
if pm.Username != "" {
q.Add("username", pm.Username)
}
if pm.FirstName != "" {
q.Add("first_name", pm.FirstName)
}
if pm.LastName != "" {
q.Add("last_name", pm.LastName)
}
if pm.Name != "" {
q.Add("name", pm.Name)
}
+32 -24
View File
@@ -17,12 +17,15 @@ import (
mggroups "github.com/absmach/magistrala/pkg/groups"
sdk "github.com/absmach/magistrala/pkg/sdk/go"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/absmach/magistrala/users"
"github.com/stretchr/testify/assert"
)
const (
invalidIdentity = "invalididentity"
Identity = "identity"
Email = "email"
InvalidEmail = "invalidemail"
secret = "strongsecret"
invalidToken = "invalid"
contentType = "application/senml+json"
@@ -51,11 +54,11 @@ func generateUUID(t *testing.T) string {
return ulid
}
func convertClients(cs []sdk.User) []mgclients.Client {
ccs := []mgclients.Client{}
func convertUsers(cs []sdk.User) []users.User {
ccs := []users.User{}
for _, c := range cs {
ccs = append(ccs, convertClient(c))
ccs = append(ccs, convertUser(c))
}
return ccs
@@ -131,29 +134,31 @@ func convertChildren(gs []*sdk.Group) []*mggroups.Group {
return cg
}
func convertClient(c sdk.User) mgclients.Client {
func convertUser(c sdk.User) users.User {
if c.Status == "" {
c.Status = mgclients.EnabledStatus.String()
c.Status = users.EnabledStatus.String()
}
status, err := mgclients.ToStatus(c.Status)
status, err := users.ToStatus(c.Status)
if err != nil {
return mgclients.Client{}
return users.User{}
}
role, err := mgclients.ToRole(c.Role)
role, err := users.ToRole(c.Role)
if err != nil {
return mgclients.Client{}
return users.User{}
}
return mgclients.Client{
ID: c.ID,
Name: c.Name,
Tags: c.Tags,
Domain: c.Domain,
Credentials: mgclients.Credentials(c.Credentials),
Metadata: mgclients.Metadata(c.Metadata),
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
Status: status,
Role: role,
return users.User{
ID: c.ID,
FirstName: c.FirstName,
LastName: c.LastName,
Tags: c.Tags,
Email: c.Email,
Credentials: users.Credentials(c.Credentials),
Metadata: users.Metadata(c.Metadata),
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
Status: status,
Role: role,
ProfilePicture: c.ProfilePicture,
}
}
@@ -229,17 +234,20 @@ func generateTestUser(t *testing.T) sdk.User {
createdAt, err := time.Parse(time.RFC3339, "2024-01-01T00:00:00Z")
assert.Nil(t, err, fmt.Sprintf("Unexpected error parsing time: %v", err))
return sdk.User{
ID: generateUUID(t),
Name: "clientname",
ID: generateUUID(t),
FirstName: "userfirstname",
LastName: "userlastname",
Email: "useremail@example.com",
Credentials: sdk.Credentials{
Identity: "clientidentity@email.com",
Username: "username",
Secret: secret,
},
Tags: []string{"tag1", "tag2"},
Metadata: validMetadata,
CreatedAt: createdAt,
UpdatedAt: createdAt,
Status: mgclients.EnabledStatus.String(),
Status: users.EnabledStatus.String(),
Role: users.UserRole.String(),
}
}
+6 -1
View File
@@ -27,7 +27,7 @@ const (
type Thing struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Credentials Credentials `json:"credentials"`
Credentials ClientCredentials `json:"credentials"`
Tags []string `json:"tags,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
@@ -37,6 +37,11 @@ type Thing struct {
Permissions []string `json:"permissions,omitempty"`
}
type ClientCredentials struct {
Identity string `json:"identity,omitempty"`
Secret string `json:"secret,omitempty"`
}
func (sdk mgSDK) CreateThing(thing Thing, domainID, token string) (Thing, errors.SDKError) {
data, err := json.Marshal(thing)
if err != nil {
+1 -1
View File
@@ -2203,7 +2203,7 @@ func generateTestThing(t *testing.T) sdk.Thing {
return sdk.Thing{
ID: testsutil.GenerateUUID(t),
Name: "clientname",
Credentials: sdk.Credentials{
Credentials: sdk.ClientCredentials{
Identity: "thing@example.com",
Secret: generateUUID(t),
},
+2 -1
View File
@@ -20,7 +20,8 @@ type Token struct {
}
type Login struct {
Identity string `json:"identity"`
Email string `json:"email"`
Username string `json:"username,omitempty"`
Secret string `json:"secret"`
}
+10 -10
View File
@@ -41,7 +41,7 @@ func TestIssueToken(t *testing.T) {
{
desc: "issue token successfully",
login: sdk.Login{
Identity: client.Credentials.Identity,
Username: client.Credentials.Username,
Secret: client.Credentials.Secret,
},
svcRes: &magistrala.Token{
@@ -54,9 +54,9 @@ func TestIssueToken(t *testing.T) {
err: nil,
},
{
desc: "issue token with invalid identity",
desc: "issue token with invalid username",
login: sdk.Login{
Identity: invalidIdentity,
Username: invalidIdentity,
Secret: client.Credentials.Secret,
},
svcRes: &magistrala.Token{},
@@ -67,7 +67,7 @@ func TestIssueToken(t *testing.T) {
{
desc: "issue token with invalid secret",
login: sdk.Login{
Identity: client.Credentials.Identity,
Username: client.Credentials.Username,
Secret: "invalid",
},
svcRes: &magistrala.Token{},
@@ -76,20 +76,20 @@ func TestIssueToken(t *testing.T) {
err: errors.NewSDKErrorWithStatus(svcerr.ErrLogin, http.StatusUnauthorized),
},
{
desc: "issue token with empty identity",
desc: "issue token with empty username",
login: sdk.Login{
Identity: "",
Username: "",
Secret: client.Credentials.Secret,
},
svcRes: &magistrala.Token{},
svcErr: nil,
response: sdk.Token{},
err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingIdentity), http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingUsername), http.StatusBadRequest),
},
{
desc: "issue token with empty secret",
login: sdk.Login{
Identity: client.Credentials.Identity,
Username: client.Credentials.Username,
Secret: "",
},
svcRes: &magistrala.Token{},
@@ -100,12 +100,12 @@ func TestIssueToken(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
svcCall := svc.On("IssueToken", mock.Anything, tc.login.Identity, tc.login.Secret).Return(tc.svcRes, tc.svcErr)
svcCall := svc.On("IssueToken", mock.Anything, tc.login.Username, tc.login.Secret).Return(tc.svcRes, tc.svcErr)
resp, err := mgsdk.CreateToken(tc.login)
assert.Equal(t, tc.err, err)
assert.Equal(t, tc.response, resp)
if tc.err == nil {
ok := svcCall.Parent.AssertCalled(t, "IssueToken", mock.Anything, tc.login.Identity, tc.login.Secret)
ok := svcCall.Parent.AssertCalled(t, "IssueToken", mock.Anything, tc.login.Username, tc.login.Secret)
assert.True(t, ok)
}
svcCall.Unset()
+63 -17
View File
@@ -27,16 +27,19 @@ const (
// User represents magistrala user its credentials.
type User struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Credentials Credentials `json:"credentials"`
Tags []string `json:"tags,omitempty"`
Domain string `json:"-"` // ignoring Domain Field, since it will be always empty for users
Metadata Metadata `json:"metadata,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
Status string `json:"status,omitempty"`
Role string `json:"role,omitempty"`
ID string `json:"id"`
Username string `json:"username,omitempty"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Email string `json:"email,omitempty"`
Credentials Credentials `json:"credentials"`
Tags []string `json:"tags,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
Status string `json:"status,omitempty"`
Role string `json:"role,omitempty"`
ProfilePicture string `json:"profile_picture,omitempty"`
}
func (sdk mgSDK) CreateUser(user User, token string) (User, errors.SDKError) {
@@ -178,15 +181,15 @@ func (sdk mgSDK) UpdateUserTags(user User, token string) (User, errors.SDKError)
return user, nil
}
func (sdk mgSDK) UpdateUserIdentity(user User, token string) (User, errors.SDKError) {
ucir := updateClientIdentityReq{token: token, id: user.ID, Identity: user.Credentials.Identity}
func (sdk mgSDK) UpdateUserEmail(user User, token string) (User, errors.SDKError) {
ucir := updateUserEmailReq{token: token, id: user.ID, Email: user.Email}
data, err := json.Marshal(ucir)
if err != nil {
return User{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/identity", sdk.usersURL, usersEndpoint, user.ID)
url := fmt.Sprintf("%s/%s/%s/email", sdk.usersURL, usersEndpoint, user.ID)
_, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
@@ -233,7 +236,7 @@ func (sdk mgSDK) ResetPassword(password, confPass, token string) errors.SDKError
}
func (sdk mgSDK) UpdatePassword(oldPass, newPass, token string) (User, errors.SDKError) {
ucsr := updateClientSecretReq{OldSecret: oldPass, NewSecret: newPass}
ucsr := updateUserSecretReq{OldSecret: oldPass, NewSecret: newPass}
data, err := json.Marshal(ucsr)
if err != nil {
@@ -276,6 +279,49 @@ func (sdk mgSDK) UpdateUserRole(user User, token string) (User, errors.SDKError)
return user, nil
}
func (sdk mgSDK) UpdateUsername(user User, token string) (User, errors.SDKError) {
uur := UpdateUsernameReq{id: user.ID, Username: user.Credentials.Username}
data, err := json.Marshal(uur)
if err != nil {
return User{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/username", sdk.usersURL, usersEndpoint, user.ID)
_, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return User{}, sdkerr
}
user = User{}
if err = json.Unmarshal(body, &user); err != nil {
return User{}, errors.NewSDKError(err)
}
return user, nil
}
func (sdk mgSDK) UpdateProfilePicture(user User, token string) (User, errors.SDKError) {
data, err := json.Marshal(user)
if err != nil {
return User{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/picture", sdk.usersURL, usersEndpoint, user.ID)
_, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return User{}, sdkerr
}
user = User{}
if err = json.Unmarshal(body, &user); err != nil {
return User{}, errors.NewSDKError(err)
}
return user, nil
}
func (sdk mgSDK) ListUserChannels(userID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) {
url, err := sdk.withQueryParams(sdk.thingsURL, fmt.Sprintf("%s/%s/%s/%s", pm.DomainID, usersEndpoint, userID, channelsEndpoint), pm)
if err != nil {
@@ -348,14 +394,14 @@ func (sdk mgSDK) SearchUsers(pm PageMetadata, token string) (UsersPage, errors.S
}
func (sdk mgSDK) EnableUser(id, token string) (User, errors.SDKError) {
return sdk.changeClientStatus(token, id, enableEndpoint)
return sdk.changeUserStatus(token, id, enableEndpoint)
}
func (sdk mgSDK) DisableUser(id, token string) (User, errors.SDKError) {
return sdk.changeClientStatus(token, id, disableEndpoint)
return sdk.changeUserStatus(token, id, disableEndpoint)
}
func (sdk mgSDK) changeClientStatus(token, id, status string) (User, errors.SDKError) {
func (sdk mgSDK) changeUserStatus(token, id, status string) (User, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, usersEndpoint, id, status)
_, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusOK)
+466 -440
View File
File diff suppressed because it is too large Load Diff
+63 -3
View File
@@ -2506,6 +2506,36 @@ func (_m *SDK) UpdatePassword(oldPass string, newPass string, token string) (sdk
return r0, r1
}
// UpdateProfilePicture provides a mock function with given fields: user, token
func (_m *SDK) UpdateProfilePicture(user sdk.User, token string) (sdk.User, errors.SDKError) {
ret := _m.Called(user, token)
if len(ret) == 0 {
panic("no return value specified for UpdateProfilePicture")
}
var r0 sdk.User
var r1 errors.SDKError
if rf, ok := ret.Get(0).(func(sdk.User, string) (sdk.User, errors.SDKError)); ok {
return rf(user, token)
}
if rf, ok := ret.Get(0).(func(sdk.User, string) sdk.User); ok {
r0 = rf(user, token)
} else {
r0 = ret.Get(0).(sdk.User)
}
if rf, ok := ret.Get(1).(func(sdk.User, string) errors.SDKError); ok {
r1 = rf(user, token)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(errors.SDKError)
}
}
return r0, r1
}
// UpdateThing provides a mock function with given fields: thing, domainID, token
func (_m *SDK) UpdateThing(thing sdk.Thing, domainID string, token string) (sdk.Thing, errors.SDKError) {
ret := _m.Called(thing, domainID, token)
@@ -2626,12 +2656,12 @@ func (_m *SDK) UpdateUser(user sdk.User, token string) (sdk.User, errors.SDKErro
return r0, r1
}
// UpdateUserIdentity provides a mock function with given fields: user, token
func (_m *SDK) UpdateUserIdentity(user sdk.User, token string) (sdk.User, errors.SDKError) {
// UpdateUserEmail provides a mock function with given fields: user, token
func (_m *SDK) UpdateUserEmail(user sdk.User, token string) (sdk.User, errors.SDKError) {
ret := _m.Called(user, token)
if len(ret) == 0 {
panic("no return value specified for UpdateUserIdentity")
panic("no return value specified for UpdateUserEmail")
}
var r0 sdk.User
@@ -2716,6 +2746,36 @@ func (_m *SDK) UpdateUserTags(user sdk.User, token string) (sdk.User, errors.SDK
return r0, r1
}
// UpdateUsername provides a mock function with given fields: user, token
func (_m *SDK) UpdateUsername(user sdk.User, token string) (sdk.User, errors.SDKError) {
ret := _m.Called(user, token)
if len(ret) == 0 {
panic("no return value specified for UpdateUsername")
}
var r0 sdk.User
var r1 errors.SDKError
if rf, ok := ret.Get(0).(func(sdk.User, string) (sdk.User, errors.SDKError)); ok {
return rf(user, token)
}
if rf, ok := ret.Get(0).(func(sdk.User, string) sdk.User); ok {
r0 = rf(user, token)
} else {
r0 = ret.Get(0).(sdk.User)
}
if rf, ok := ret.Get(1).(func(sdk.User, string) errors.SDKError); ok {
r1 = rf(user, token)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(errors.SDKError)
}
}
return r0, r1
}
// User provides a mock function with given fields: id, token
func (_m *SDK) User(id string, token string) (sdk.User, errors.SDKError) {
ret := _m.Called(id, token)
+2 -1
View File
@@ -25,7 +25,8 @@ type ServiceConf struct {
ThingsURL string `toml:"things_url" env:"MG_PROVISION_THINGS_LOCATION" envDefault:"http://localhost"`
UsersURL string `toml:"users_url" env:"MG_PROVISION_USERS_LOCATION" envDefault:"http://localhost"`
HTTPPort string `toml:"http_port" env:"MG_PROVISION_HTTP_PORT" envDefault:"9016"`
MgUser string `toml:"mg_user" env:"MG_PROVISION_USER" envDefault:"test@example.com"`
MgEmail string `toml:"mg_email" env:"MG_PROVISION_EMAIL" envDefault:"test@example.com"`
MgUsername string `toml:"mg_username" env:"MG_PROVISION_USERNAME" envDefault:"user"`
MgPass string `toml:"mg_pass" env:"MG_PROVISION_PASS" envDefault:"test"`
MgDomainID string `toml:"mg_domain_id" env:"MG_PROVISION_DOMAIN_ID" envDefault:""`
MgAPIKey string `toml:"mg_api_key" env:"MG_PROVISION_API_KEY" envDefault:""`
+3 -2
View File
@@ -289,12 +289,13 @@ func (ps *provisionService) createTokenIfEmpty(token string) (string, error) {
}
// If no API key use username and password provided to create access token.
if ps.conf.Server.MgUser == "" || ps.conf.Server.MgPass == "" {
if ps.conf.Server.MgUsername == "" || ps.conf.Server.MgPass == "" {
return token, ErrMissingCredentials
}
u := sdk.Login{
Identity: ps.conf.Server.MgUser,
Email: ps.conf.Server.MgEmail,
Username: ps.conf.Server.MgUsername,
Secret: ps.conf.Server.MgPass,
}
tkn, err := ps.sdk.CreateToken(u)
+3 -3
View File
@@ -111,7 +111,7 @@ func TestCert(t *testing.T) {
desc: "empty token with username and password",
config: provision.Config{
Server: provision.ServiceConf{
MgUser: "test@example.com",
MgUsername: "testUsername",
MgPass: "12345678",
MgDomainID: testsutil.GenerateUUID(t),
},
@@ -132,7 +132,7 @@ func TestCert(t *testing.T) {
desc: "empty token with username and invalid password",
config: provision.Config{
Server: provision.ServiceConf{
MgUser: "test@example.com",
MgUsername: "testUsername",
MgPass: "12345678",
MgDomainID: testsutil.GenerateUUID(t),
},
@@ -219,7 +219,7 @@ func TestCert(t *testing.T) {
mgsdk.On("IssueCert", c.thingID, c.config.Cert.TTL, c.domainID, mock.Anything).Return(sdk.Cert{SerialNumber: c.serial}, c.sdkCertErr)
mgsdk.On("ViewCert", c.serial, mock.Anything, mock.Anything).Return(sdk.Cert{Certificate: c.cert, Key: c.key}, c.sdkCertErr)
login := sdk.Login{
Identity: c.config.Server.MgUser,
Username: c.config.Server.MgUsername,
Secret: c.config.Server.MgPass,
}
mgsdk.On("CreateToken", login).Return(sdk.Token{AccessToken: validToken}, c.sdkTokenErr)
+1 -1
View File
@@ -38,7 +38,7 @@ done
###
# Users
###
MG_USERS_LOG_LEVEL=info MG_USERS_HTTP_PORT=9002 MG_USERS_GRPC_PORT=7001 MG_USERS_ADMIN_EMAIL=admin@magistrala.com MG_USERS_ADMIN_PASSWORD=12345678 MG_EMAIL_TEMPLATE=../docker/templates/users.tmpl $BUILD_DIR/magistrala-users &
MG_USERS_LOG_LEVEL=info MG_USERS_HTTP_PORT=9002 MG_USERS_GRPC_PORT=7001 MG_USERS_ADMIN_EMAIL=admin@magistrala.com MG_USERS_ADMIN_PASSWORD=12345678 MG_USERS_ADMIN_USERNAME=admin MG_EMAIL_TEMPLATE=../docker/templates/users.tmpl $BUILD_DIR/magistrala-users &
###
# Things
+2 -3
View File
@@ -11,7 +11,6 @@ import (
"testing"
"time"
"github.com/absmach/magistrala/pkg/postgres"
pgclient "github.com/absmach/magistrala/pkg/postgres"
cpostgres "github.com/absmach/magistrala/things/postgres"
"github.com/jmoiron/sqlx"
@@ -22,7 +21,7 @@ import (
var (
db *sqlx.DB
database postgres.Database
database pgclient.Database
tracer = otel.Tracer("repo_tests")
)
@@ -84,7 +83,7 @@ func TestMain(m *testing.M) {
log.Fatalf("Could not setup test DB connection: %s", err)
}
database = postgres.NewDatabase(db, dbConfig, tracer)
database = pgclient.NewDatabase(db, dbConfig, tracer)
code := m.Run()
+31 -19
View File
@@ -133,9 +133,11 @@ func errExit(err error) {
func createUser(s sdk.SDK, conf Config) (string, string, error) {
user := sdk.User{
Name: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()),
FirstName: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()),
LastName: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()),
Email: fmt.Sprintf("%s%s@email.com", conf.Prefix, namesgenerator.Generate()),
Credentials: sdk.Credentials{
Identity: fmt.Sprintf("%s%s@email.com", conf.Prefix, namesgenerator.Generate()),
Username: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()),
Secret: defPass,
},
Status: sdk.EnabledStatus,
@@ -147,7 +149,7 @@ func createUser(s sdk.SDK, conf Config) (string, string, error) {
}
login := sdk.Login{
Identity: user.Credentials.Identity,
Username: user.Credentials.Username,
Secret: user.Credentials.Secret,
}
token, err := s.CreateToken(login)
@@ -168,7 +170,7 @@ func createUser(s sdk.SDK, conf Config) (string, string, error) {
}
login = sdk.Login{
Identity: user.Credentials.Identity,
Username: user.Credentials.Username,
Secret: user.Credentials.Secret,
}
token, err = s.CreateToken(login)
@@ -185,9 +187,11 @@ func createUsers(s sdk.SDK, conf Config, token string) ([]sdk.User, error) {
for i := uint64(0); i < conf.Num; i++ {
user := sdk.User{
Name: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()),
FirstName: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()),
LastName: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()),
Email: fmt.Sprintf("%s%s@email.com", conf.Prefix, namesgenerator.Generate()),
Credentials: sdk.Credentials{
Identity: fmt.Sprintf("%s%s@email.com", conf.Prefix, namesgenerator.Generate()),
Username: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()),
Secret: defPass,
},
Status: sdk.EnabledStatus,
@@ -249,19 +253,19 @@ func createThings(s sdk.SDK, conf Config, domainID, token string) ([]sdk.Thing,
for i := 0; i < batches; i++ {
ths, err := createThingsInBatch(s, conf, domainID, token, batchSize)
if err != nil {
return []sdk.Thing{}, fmt.Errorf("Failed to create the things: %w", err)
return []sdk.Thing{}, fmt.Errorf("failed to create the things: %w", err)
}
things = append(things, ths...)
}
ths, err := createThingsInBatch(s, conf, domainID, token, conf.Num%uint64(batchSize))
if err != nil {
return []sdk.Thing{}, fmt.Errorf("Failed to create the things: %w", err)
return []sdk.Thing{}, fmt.Errorf("failed to create the things: %w", err)
}
things = append(things, ths...)
} else {
ths, err := createThingsInBatch(s, conf, domainID, token, conf.Num)
if err != nil {
return []sdk.Thing{}, fmt.Errorf("Failed to create the things: %w", err)
return []sdk.Thing{}, fmt.Errorf("failed to create the things: %w", err)
}
things = append(things, ths...)
}
@@ -294,19 +298,19 @@ func createChannels(s sdk.SDK, conf Config, domainID, token string) ([]sdk.Chann
for i := 0; i < batches; i++ {
chs, err := createChannelsInBatch(s, conf, token, domainID, batchSize)
if err != nil {
return []sdk.Channel{}, fmt.Errorf("Failed to create the channels: %w", err)
return []sdk.Channel{}, fmt.Errorf("failed to create the channels: %w", err)
}
channels = append(channels, chs...)
}
chs, err := createChannelsInBatch(s, conf, domainID, token, conf.Num%uint64(batchSize))
if err != nil {
return []sdk.Channel{}, fmt.Errorf("Failed to create the channels: %w", err)
return []sdk.Channel{}, fmt.Errorf("failed to create the channels: %w", err)
}
channels = append(channels, chs...)
} else {
chs, err := createChannelsInBatch(s, conf, domainID, token, conf.Num)
if err != nil {
return []sdk.Channel{}, fmt.Errorf("Failed to create the channels: %w", err)
return []sdk.Channel{}, fmt.Errorf("failed to create the channels: %w", err)
}
channels = append(channels, chs...)
}
@@ -369,26 +373,34 @@ func read(s sdk.SDK, conf Config, domainID, token string, users []sdk.User, grou
func update(s sdk.SDK, domainID, token string, users []sdk.User, groups []sdk.Group, things []sdk.Thing, channels []sdk.Channel) error {
for _, user := range users {
user.Name = namesgenerator.Generate()
user.FirstName = namesgenerator.Generate()
user.Metadata = sdk.Metadata{"Update": namesgenerator.Generate()}
rUser, err := s.UpdateUser(user, token)
if err != nil {
return fmt.Errorf("failed to update user %w", err)
}
if rUser.Name != user.Name {
return fmt.Errorf("failed to update user name before %s after %s", user.Name, rUser.Name)
if rUser.FirstName != user.FirstName {
return fmt.Errorf("failed to update user name before %s after %s", user.FirstName, rUser.FirstName)
}
if rUser.Metadata["Update"] != user.Metadata["Update"] {
return fmt.Errorf("failed to update user metadata before %s after %s", user.Metadata["Update"], rUser.Metadata["Update"])
}
user = rUser
user.Credentials.Identity = namesgenerator.Generate()
rUser, err = s.UpdateUserIdentity(user, token)
user.Credentials.Username = namesgenerator.Generate()
rUser, err = s.UpdateUsername(user, token)
if err != nil {
return fmt.Errorf("failed to update username %w", err)
}
if rUser.Credentials.Username != user.Credentials.Username {
return fmt.Errorf("failed to update user name before %s after %s", user.Credentials.Username, rUser.Credentials.Username)
}
user = rUser
rUser, err = s.UpdateUserEmail(user, token)
if err != nil {
return fmt.Errorf("failed to update user identity %w", err)
}
if rUser.Credentials.Identity != user.Credentials.Identity {
return fmt.Errorf("failed to update user identity before %s after %s", user.Credentials.Identity, rUser.Credentials.Identity)
if rUser.Email != user.Email {
return fmt.Errorf("failed to update user identity before %s after %s", user.Email, rUser.Email)
}
user = rUser
user.Tags = []string{namesgenerator.Generate()}
+7 -5
View File
@@ -44,6 +44,7 @@ type MgConn struct {
type Config struct {
Host string
Username string
Email string
Password string
Num int
SSL bool
@@ -74,14 +75,15 @@ func Provision(conf Config) error {
s := sdk.NewSDK(sdkConf)
user := sdk.User{
Email: conf.Email,
Credentials: sdk.Credentials{
Identity: conf.Username,
Username: conf.Username,
Secret: conf.Password,
},
}
if user.Credentials.Identity == "" {
user.Credentials.Identity = fmt.Sprintf("%s@email.com", namesgenerator.Generate())
if user.Email == "" {
user.Email = fmt.Sprintf("%s@email.com", namesgenerator.Generate())
user.Credentials.Secret = defPass
}
@@ -93,7 +95,7 @@ func Provision(conf Config) error {
var err error
// Login user
token, err := s.CreateToken(sdk.Login{Identity: user.Credentials.Identity, Secret: user.Credentials.Secret})
token, err := s.CreateToken(sdk.Login{Username: user.Credentials.Username, Secret: user.Credentials.Secret})
if err != nil {
return fmt.Errorf("unable to login user: %s", err.Error())
}
@@ -112,7 +114,7 @@ func Provision(conf Config) error {
}
// Login to domain
token, err = s.CreateToken(sdk.Login{
Identity: user.Credentials.Identity,
Username: user.Credentials.Username,
Secret: user.Credentials.Secret,
})
if err != nil {
+1 -1
View File
@@ -1,4 +1,4 @@
# Clients
# Users
Users service provides an HTTP API for managing users. Through this API clients are able to do the following actions:
+601 -569
View File
File diff suppressed because it is too large Load Diff
+141 -90
View File
@@ -9,7 +9,6 @@ import (
"github.com/absmach/magistrala/internal/api"
"github.com/absmach/magistrala/pkg/apiutil"
"github.com/absmach/magistrala/pkg/authn"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/pkg/errors"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
"github.com/absmach/magistrala/users"
@@ -18,7 +17,7 @@ import (
func registrationEndpoint(svc users.Service, selfRegister bool) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(createClientReq)
req := request.(createUserReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -32,21 +31,21 @@ func registrationEndpoint(svc users.Service, selfRegister bool) endpoint.Endpoin
}
}
client, err := svc.RegisterClient(ctx, session, req.client, selfRegister)
user, err := svc.Register(ctx, session, req.User, selfRegister)
if err != nil {
return nil, err
}
return createClientRes{
Client: client,
return createUserRes{
User: user,
created: true,
}, nil
}
}
func viewClientEndpoint(svc users.Service) endpoint.Endpoint {
func viewEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(viewClientReq)
req := request.(viewUserReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -55,12 +54,12 @@ func viewClientEndpoint(svc users.Service) endpoint.Endpoint {
if !ok {
return nil, svcerr.ErrAuthorization
}
client, err := svc.ViewClient(ctx, session, req.id)
user, err := svc.View(ctx, session, req.id)
if err != nil {
return nil, err
}
return viewClientRes{Client: client}, nil
return viewUserRes{User: user}, nil
}
}
@@ -75,13 +74,13 @@ func viewProfileEndpoint(svc users.Service) endpoint.Endpoint {
return nil, err
}
return viewClientRes{Client: client}, nil
return viewUserRes{User: client}, nil
}
}
func listClientsEndpoint(svc users.Service) endpoint.Endpoint {
func listUsersEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listClientsReq)
req := request.(listUsersReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -91,70 +90,74 @@ func listClientsEndpoint(svc users.Service) endpoint.Endpoint {
return nil, svcerr.ErrAuthorization
}
pm := mgclients.Page{
Status: req.status,
Offset: req.offset,
Limit: req.limit,
Name: req.name,
Tag: req.tag,
Metadata: req.metadata,
Identity: req.identity,
Order: req.order,
Dir: req.dir,
Id: req.id,
pm := users.Page{
Status: req.status,
Offset: req.offset,
Limit: req.limit,
Username: req.userName,
Tag: req.tag,
Metadata: req.metadata,
FirstName: req.firstName,
LastName: req.lastName,
Email: req.email,
Order: req.order,
Dir: req.dir,
Id: req.id,
}
page, err := svc.ListClients(ctx, session, pm)
page, err := svc.ListUsers(ctx, session, pm)
if err != nil {
return nil, err
}
res := clientsPageRes{
res := usersPageRes{
pageRes: pageRes{
Total: page.Total,
Offset: page.Offset,
Limit: page.Limit,
},
Clients: []viewClientRes{},
Users: []viewUserRes{},
}
for _, client := range page.Clients {
res.Clients = append(res.Clients, viewClientRes{Client: client})
for _, user := range page.Users {
res.Users = append(res.Users, viewUserRes{User: user})
}
return res, nil
}
}
func searchClientsEndpoint(svc users.Service) endpoint.Endpoint {
func searchUsersEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(searchClientsReq)
req := request.(searchUsersReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
pm := mgclients.Page{
Offset: req.Offset,
Limit: req.Limit,
Name: req.Name,
Id: req.Id,
Order: req.Order,
Dir: req.Dir,
pm := users.Page{
Offset: req.Offset,
Limit: req.Limit,
Username: req.Username,
FirstName: req.FirstName,
LastName: req.LastName,
Id: req.Id,
Order: req.Order,
Dir: req.Dir,
}
page, err := svc.SearchUsers(ctx, pm)
if err != nil {
return nil, err
}
res := clientsPageRes{
res := usersPageRes{
pageRes: pageRes{
Total: page.Total,
Offset: page.Offset,
Limit: page.Limit,
},
Clients: []viewClientRes{},
Users: []viewUserRes{},
}
for _, client := range page.Clients {
res.Clients = append(res.Clients, viewClientRes{Client: client})
for _, user := range page.Users {
res.Users = append(res.Users, viewUserRes{User: user})
}
return res, nil
@@ -179,7 +182,7 @@ func listMembersByGroupEndpoint(svc users.Service) endpoint.Endpoint {
return nil, err
}
return buildClientsResponse(page), nil
return buildUsersResponse(page), nil
}
}
@@ -202,7 +205,7 @@ func listMembersByChannelEndpoint(svc users.Service) endpoint.Endpoint {
return nil, err
}
return buildClientsResponse(page), nil
return buildUsersResponse(page), nil
}
}
@@ -224,7 +227,7 @@ func listMembersByThingEndpoint(svc users.Service) endpoint.Endpoint {
return nil, err
}
return buildClientsResponse(page), nil
return buildUsersResponse(page), nil
}
}
@@ -246,13 +249,13 @@ func listMembersByDomainEndpoint(svc users.Service) endpoint.Endpoint {
return nil, err
}
return buildClientsResponse(page), nil
return buildUsersResponse(page), nil
}
}
func updateClientEndpoint(svc users.Service) endpoint.Endpoint {
func updateEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(updateClientReq)
req := request.(updateUserReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -262,24 +265,25 @@ func updateClientEndpoint(svc users.Service) endpoint.Endpoint {
return nil, svcerr.ErrAuthorization
}
client := mgclients.Client{
ID: req.id,
Name: req.Name,
Metadata: req.Metadata,
user := users.User{
ID: req.id,
FirstName: req.FirstName,
LastName: req.LastName,
Metadata: req.Metadata,
}
client, err := svc.UpdateClient(ctx, session, client)
user, err := svc.Update(ctx, session, user)
if err != nil {
return nil, err
}
return updateClientRes{Client: client}, nil
return updateUserRes{User: user}, nil
}
}
func updateClientTagsEndpoint(svc users.Service) endpoint.Endpoint {
func updateTagsEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(updateClientTagsReq)
req := request.(updateUserTagsReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -289,23 +293,23 @@ func updateClientTagsEndpoint(svc users.Service) endpoint.Endpoint {
return nil, svcerr.ErrAuthorization
}
client := mgclients.Client{
user := users.User{
ID: req.id,
Tags: req.Tags,
}
client, err := svc.UpdateClientTags(ctx, session, client)
user, err := svc.UpdateTags(ctx, session, user)
if err != nil {
return nil, err
}
return updateClientRes{Client: client}, nil
return updateUserRes{User: user}, nil
}
}
func updateClientIdentityEndpoint(svc users.Service) endpoint.Endpoint {
func updateEmailEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(updateClientIdentityReq)
req := request.(updateEmailReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -315,12 +319,12 @@ func updateClientIdentityEndpoint(svc users.Service) endpoint.Endpoint {
return nil, svcerr.ErrAuthorization
}
client, err := svc.UpdateClientIdentity(ctx, session, req.id, req.Identity)
user, err := svc.UpdateEmail(ctx, session, req.id, req.Email)
if err != nil {
return nil, err
}
return updateClientRes{Client: client}, nil
return updateUserRes{User: user}, nil
}
}
@@ -371,9 +375,9 @@ func passwordResetEndpoint(svc users.Service) endpoint.Endpoint {
}
}
func updateClientSecretEndpoint(svc users.Service) endpoint.Endpoint {
func updateSecretEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(updateClientSecretReq)
req := request.(updateUserSecretReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -382,23 +386,70 @@ func updateClientSecretEndpoint(svc users.Service) endpoint.Endpoint {
if !ok {
return nil, svcerr.ErrAuthorization
}
client, err := svc.UpdateClientSecret(ctx, session, req.OldSecret, req.NewSecret)
user, err := svc.UpdateSecret(ctx, session, req.OldSecret, req.NewSecret)
if err != nil {
return nil, err
}
return updateClientRes{Client: client}, nil
return updateUserRes{User: user}, nil
}
}
func updateClientRoleEndpoint(svc users.Service) endpoint.Endpoint {
func updateUsernameEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(updateClientRoleReq)
req := request.(updateUsernameReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
client := mgclients.Client{
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
user, err := svc.UpdateUsername(ctx, session, req.id, req.Username)
if err != nil {
return nil, err
}
return updateUserRes{User: user}, nil
}
}
func updateProfilePictureEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(updateProfilePictureReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
user := users.User{
ID: req.id,
ProfilePicture: req.ProfilePicture,
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
user, err := svc.Update(ctx, session, user)
if err != nil {
return nil, err
}
return updateUserRes{User: user}, nil
}
}
func updateRoleEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(updateUserRoleReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
user := users.User{
ID: req.id,
Role: req.role,
}
@@ -408,23 +459,23 @@ func updateClientRoleEndpoint(svc users.Service) endpoint.Endpoint {
return nil, svcerr.ErrAuthorization
}
client, err := svc.UpdateClientRole(ctx, session, client)
user, err := svc.UpdateRole(ctx, session, user)
if err != nil {
return nil, err
}
return updateClientRes{Client: client}, nil
return updateUserRes{User: user}, nil
}
}
func issueTokenEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(loginClientReq)
req := request.(loginUserReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
token, err := svc.IssueToken(ctx, req.Identity, req.Secret)
token, err := svc.IssueToken(ctx, req.Username, req.Secret)
if err != nil {
return nil, err
}
@@ -462,9 +513,9 @@ func refreshTokenEndpoint(svc users.Service) endpoint.Endpoint {
}
}
func enableClientEndpoint(svc users.Service) endpoint.Endpoint {
func enableEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(changeClientStatusReq)
req := request.(changeUserStatusReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -474,18 +525,18 @@ func enableClientEndpoint(svc users.Service) endpoint.Endpoint {
return nil, svcerr.ErrAuthorization
}
client, err := svc.EnableClient(ctx, session, req.id)
user, err := svc.Enable(ctx, session, req.id)
if err != nil {
return nil, err
}
return changeClientStatusClientRes{Client: client}, nil
return changeUserStatusRes{User: user}, nil
}
}
func disableClientEndpoint(svc users.Service) endpoint.Endpoint {
func disableEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(changeClientStatusReq)
req := request.(changeUserStatusReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -495,18 +546,18 @@ func disableClientEndpoint(svc users.Service) endpoint.Endpoint {
return nil, svcerr.ErrAuthorization
}
client, err := svc.DisableClient(ctx, session, req.id)
user, err := svc.Disable(ctx, session, req.id)
if err != nil {
return nil, err
}
return changeClientStatusClientRes{Client: client}, nil
return changeUserStatusRes{User: user}, nil
}
}
func deleteClientEndpoint(svc users.Service) endpoint.Endpoint {
func deleteEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(changeClientStatusReq)
req := request.(changeUserStatusReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -516,26 +567,26 @@ func deleteClientEndpoint(svc users.Service) endpoint.Endpoint {
return nil, svcerr.ErrAuthorization
}
if err := svc.DeleteClient(ctx, session, req.id); err != nil {
if err := svc.Delete(ctx, session, req.id); err != nil {
return nil, err
}
return deleteClientRes{true}, nil
return deleteUserRes{true}, nil
}
}
func buildClientsResponse(cp mgclients.MembersPage) clientsPageRes {
res := clientsPageRes{
func buildUsersResponse(cp users.MembersPage) usersPageRes {
res := usersPageRes{
pageRes: pageRes{
Total: cp.Total,
Offset: cp.Offset,
Limit: cp.Limit,
},
Clients: []viewClientRes{},
Users: []viewUserRes{},
}
for _, client := range cp.Members {
res.Clients = append(res.Clients, viewClientRes{Client: client})
for _, user := range cp.Members {
res.Users = append(res.Users, viewUserRes{User: user})
}
return res
+128 -57
View File
@@ -4,39 +4,71 @@
package api
import (
"net/mail"
"net/url"
"github.com/absmach/magistrala/internal/api"
"github.com/absmach/magistrala/pkg/apiutil"
mgclients "github.com/absmach/magistrala/pkg/clients"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
"github.com/absmach/magistrala/users"
)
const maxLimitSize = 100
type createClientReq struct {
client mgclients.Client
type createUserReq struct {
users.User
}
func (req createClientReq) validate() error {
if len(req.client.Name) > api.MaxNameSize {
func (req createUserReq) validate() error {
if len(req.User.FirstName) > api.MaxNameSize {
return apiutil.ErrNameSize
}
if req.client.Credentials.Identity == "" {
return apiutil.ErrMissingIdentity
if len(req.User.LastName) > api.MaxNameSize {
return apiutil.ErrNameSize
}
if req.client.Credentials.Secret == "" {
if req.User.FirstName == "" {
return apiutil.ErrMissingFirstName
}
if req.User.LastName == "" {
return apiutil.ErrMissingLastName
}
if req.User.Credentials.Username == "" {
return apiutil.ErrMissingUsername
}
// Username must not be a valid email format due to username/email login.
if _, err := mail.ParseAddress(req.User.Credentials.Username); err == nil {
return apiutil.ErrInvalidUsername
}
if req.User.Email == "" {
return apiutil.ErrMissingEmail
}
// Email must be in a valid format.
if _, err := mail.ParseAddress(req.User.Email); err != nil {
return apiutil.ErrInvalidEmail
}
if req.User.Credentials.Secret == "" {
return apiutil.ErrMissingPass
}
if !passRegex.MatchString(req.client.Credentials.Secret) {
if !passRegex.MatchString(req.User.Credentials.Secret) {
return apiutil.ErrPasswordFormat
}
if req.User.Status == users.AllStatus {
return svcerr.ErrInvalidStatus
}
if req.User.ProfilePicture != "" {
if _, err := url.Parse(req.User.ProfilePicture); err != nil {
return apiutil.ErrInvalidProfilePictureURL
}
}
return req.client.Validate()
return req.User.Validate()
}
type viewClientReq struct {
type viewUserReq struct {
id string
}
func (req viewClientReq) validate() error {
func (req viewUserReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
@@ -44,20 +76,22 @@ func (req viewClientReq) validate() error {
return nil
}
type listClientsReq struct {
status mgclients.Status
offset uint64
limit uint64
name string
tag string
identity string
metadata mgclients.Metadata
order string
dir string
id string
type listUsersReq struct {
status users.Status
offset uint64
limit uint64
userName string
tag string
firstName string
lastName string
email string
metadata users.Metadata
order string
dir string
id string
}
func (req listClientsReq) validate() error {
func (req listUsersReq) validate() error {
if req.limit > maxLimitSize || req.limit < 1 {
return apiutil.ErrLimitSize
}
@@ -68,17 +102,19 @@ func (req listClientsReq) validate() error {
return nil
}
type searchClientsReq struct {
Offset uint64
Limit uint64
Name string
Id string
Order string
Dir string
type searchUsersReq struct {
Offset uint64
Limit uint64
Username string
FirstName string
LastName string
Id string
Order string
Dir string
}
func (req searchClientsReq) validate() error {
if req.Name == "" && req.Id == "" {
func (req searchUsersReq) validate() error {
if req.Username == "" && req.Id == "" && req.FirstName == "" && req.LastName == "" {
return apiutil.ErrEmptySearchQuery
}
@@ -86,7 +122,7 @@ func (req searchClientsReq) validate() error {
}
type listMembersByObjectReq struct {
mgclients.Page
users.Page
objectKind string
objectID string
}
@@ -102,13 +138,14 @@ func (req listMembersByObjectReq) validate() error {
return nil
}
type updateClientReq struct {
id string
Name string `json:"name,omitempty"`
Metadata mgclients.Metadata `json:"metadata,omitempty"`
type updateUserReq struct {
id string
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Metadata users.Metadata `json:"metadata,omitempty"`
}
func (req updateClientReq) validate() error {
func (req updateUserReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
@@ -116,12 +153,12 @@ func (req updateClientReq) validate() error {
return nil
}
type updateClientTagsReq struct {
type updateUserTagsReq struct {
id string
Tags []string `json:"tags,omitempty"`
}
func (req updateClientTagsReq) validate() error {
func (req updateUserTagsReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
@@ -129,13 +166,13 @@ func (req updateClientTagsReq) validate() error {
return nil
}
type updateClientRoleReq struct {
type updateUserRoleReq struct {
id string
role mgclients.Role
role users.Role
Role string `json:"role,omitempty"`
}
func (req updateClientRoleReq) validate() error {
func (req updateUserRoleReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
@@ -143,25 +180,28 @@ func (req updateClientRoleReq) validate() error {
return nil
}
type updateClientIdentityReq struct {
id string
Identity string `json:"identity,omitempty"`
type updateEmailReq struct {
id string
Email string `json:"email,omitempty"`
}
func (req updateClientIdentityReq) validate() error {
func (req updateEmailReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
if _, err := mail.ParseAddress(req.Email); err != nil {
return apiutil.ErrInvalidEmail
}
return nil
}
type updateClientSecretReq struct {
type updateUserSecretReq struct {
OldSecret string `json:"old_secret,omitempty"`
NewSecret string `json:"new_secret,omitempty"`
}
func (req updateClientSecretReq) validate() error {
func (req updateUserSecretReq) validate() error {
if req.OldSecret == "" || req.NewSecret == "" {
return apiutil.ErrMissingPass
}
@@ -172,11 +212,42 @@ func (req updateClientSecretReq) validate() error {
return nil
}
type changeClientStatusReq struct {
type updateUsernameReq struct {
id string
Username string
}
func (req updateUsernameReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
if len(req.Username) > api.MaxNameSize {
return apiutil.ErrNameSize
}
return nil
}
type updateProfilePictureReq struct {
id string
ProfilePicture string `json:"profile_picture,omitempty"`
}
func (req updateProfilePictureReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
if _, err := url.Parse(req.ProfilePicture); err != nil {
return apiutil.ErrInvalidProfilePictureURL
}
return nil
}
type changeUserStatusReq struct {
id string
}
func (req changeClientStatusReq) validate() error {
func (req changeUserStatusReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
@@ -184,14 +255,14 @@ func (req changeClientStatusReq) validate() error {
return nil
}
type loginClientReq struct {
Identity string `json:"identity,omitempty"`
type loginUserReq struct {
Username string `json:"username,omitempty"`
Secret string `json:"secret,omitempty"`
}
func (req loginClientReq) validate() error {
if req.Identity == "" {
return apiutil.ErrMissingIdentity
func (req loginUserReq) validate() error {
if req.Username == "" {
return apiutil.ErrMissingUsername
}
if req.Secret == "" {
return apiutil.ErrMissingPass
+199 -119
View File
@@ -4,13 +4,14 @@
package api
import (
"net/url"
"strings"
"testing"
"github.com/absmach/magistrala/internal/api"
"github.com/absmach/magistrala/internal/testsutil"
"github.com/absmach/magistrala/pkg/apiutil"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/users"
"github.com/stretchr/testify/assert"
)
@@ -18,7 +19,7 @@ const (
valid = "valid"
invalid = "invalid"
secret = "QJg58*aMan7j"
name = "client"
name = "user"
)
var (
@@ -26,20 +27,22 @@ var (
domain = testsutil.GenerateUUID(&testing.T{})
)
func TestCreateClientReqValidate(t *testing.T) {
func TestCreateUserReqValidate(t *testing.T) {
cases := []struct {
desc string
req createClientReq
req createUserReq
err error
}{
{
desc: "valid request",
req: createClientReq{
client: mgclients.Client{
ID: validID,
Name: valid,
Credentials: mgclients.Credentials{
Identity: "example@example.com",
req: createUserReq{
User: users.User{
ID: validID,
FirstName: valid,
LastName: valid,
Email: "example@domain.com",
Credentials: users.Credentials{
Username: "example",
Secret: secret,
},
},
@@ -48,35 +51,40 @@ func TestCreateClientReqValidate(t *testing.T) {
},
{
desc: "name too long",
req: createClientReq{
client: mgclients.Client{
ID: validID,
Name: strings.Repeat("a", api.MaxNameSize+1),
req: createUserReq{
User: users.User{
ID: validID,
FirstName: strings.Repeat("a", api.MaxNameSize+1),
LastName: valid,
},
},
err: apiutil.ErrNameSize,
},
{
desc: "missing identity in request",
req: createClientReq{
client: mgclients.Client{
ID: validID,
Name: valid,
Credentials: mgclients.Credentials{
Secret: valid,
desc: "missing email in request",
req: createUserReq{
User: users.User{
ID: validID,
FirstName: valid,
LastName: valid,
Credentials: users.Credentials{
Username: "example",
Secret: secret,
},
},
},
err: apiutil.ErrMissingIdentity,
err: apiutil.ErrMissingEmail,
},
{
desc: "missing secret in request",
req: createClientReq{
client: mgclients.Client{
ID: validID,
Name: valid,
Credentials: mgclients.Credentials{
Identity: "example@example.com",
req: createUserReq{
User: users.User{
ID: validID,
FirstName: valid,
LastName: valid,
Email: "example@domain.com",
Credentials: users.Credentials{
Username: "example",
},
},
},
@@ -84,12 +92,14 @@ func TestCreateClientReqValidate(t *testing.T) {
},
{
desc: "invalid secret in request",
req: createClientReq{
client: mgclients.Client{
ID: validID,
Name: valid,
Credentials: mgclients.Credentials{
Identity: "example@example.com",
req: createUserReq{
User: users.User{
ID: validID,
FirstName: valid,
LastName: valid,
Email: "example@domain.com",
Credentials: users.Credentials{
Username: "example",
Secret: "invalid",
},
},
@@ -99,26 +109,26 @@ func TestCreateClientReqValidate(t *testing.T) {
}
for _, tc := range cases {
err := tc.req.validate()
assert.Equal(t, tc.err, err)
assert.Equal(t, tc.err, err, "%s: expected %s got %s\n", tc.desc, tc.err, err)
}
}
func TestViewClientReqValidate(t *testing.T) {
func TestViewUserReqValidate(t *testing.T) {
cases := []struct {
desc string
req viewClientReq
req viewUserReq
err error
}{
{
desc: "valid request",
req: viewClientReq{
req: viewUserReq{
id: validID,
},
err: nil,
},
{
desc: "empty id",
req: viewClientReq{
req: viewUserReq{
id: "",
},
err: apiutil.ErrMissingID,
@@ -130,36 +140,36 @@ func TestViewClientReqValidate(t *testing.T) {
}
}
func TestListClientsReqValidate(t *testing.T) {
func TestListUsersReqValidate(t *testing.T) {
cases := []struct {
desc string
req listClientsReq
req listUsersReq
err error
}{
{
desc: "valid request",
req: listClientsReq{
req: listUsersReq{
limit: 10,
},
err: nil,
},
{
desc: "limit too big",
req: listClientsReq{
req: listUsersReq{
limit: api.MaxLimitSize + 1,
},
err: apiutil.ErrLimitSize,
},
{
desc: "limit too small",
req: listClientsReq{
req: listUsersReq{
limit: 0,
},
err: apiutil.ErrLimitSize,
},
{
desc: "invalid direction",
req: listClientsReq{
req: listUsersReq{
limit: 10,
dir: "invalid",
},
@@ -172,22 +182,22 @@ func TestListClientsReqValidate(t *testing.T) {
}
}
func TestSearchClientsReqValidate(t *testing.T) {
func TestSearchUsersReqValidate(t *testing.T) {
cases := []struct {
desc string
req searchClientsReq
req searchUsersReq
err error
}{
{
desc: "valid request",
req: searchClientsReq{
Name: name,
req: searchUsersReq{
Username: name,
},
err: nil,
},
{
desc: "empty query",
req: searchClientsReq{},
req: searchUsersReq{},
err: apiutil.ErrEmptySearchQuery,
},
}
@@ -234,25 +244,23 @@ func TestListMembersByObjectReqValidate(t *testing.T) {
}
}
func TestUpdateClientReqValidate(t *testing.T) {
func TestUpdateUserReqValidate(t *testing.T) {
cases := []struct {
desc string
req updateClientReq
req updateUserReq
err error
}{
{
desc: "valid request",
req: updateClientReq{
id: validID,
Name: valid,
req: updateUserReq{
id: validID,
},
err: nil,
},
{
desc: "empty id",
req: updateClientReq{
id: "",
Name: valid,
req: updateUserReq{
id: "",
},
err: apiutil.ErrMissingID,
},
@@ -263,15 +271,15 @@ func TestUpdateClientReqValidate(t *testing.T) {
}
}
func TestUpdateClientTagsReqValidate(t *testing.T) {
func TestUpdateUserTagsReqValidate(t *testing.T) {
cases := []struct {
desc string
req updateClientTagsReq
req updateUserTagsReq
err error
}{
{
desc: "valid request",
req: updateClientTagsReq{
req: updateUserTagsReq{
id: validID,
Tags: []string{"tag1", "tag2"},
},
@@ -279,7 +287,7 @@ func TestUpdateClientTagsReqValidate(t *testing.T) {
},
{
desc: "empty id",
req: updateClientTagsReq{
req: updateUserTagsReq{
id: "",
Tags: []string{"tag1", "tag2"},
},
@@ -292,54 +300,97 @@ func TestUpdateClientTagsReqValidate(t *testing.T) {
}
}
func TestUpdateClientRoleReqValidate(t *testing.T) {
func TestUpdateUsernameReqValidate(t *testing.T) {
cases := []struct {
desc string
req updateClientRoleReq
req updateUsernameReq
err error
}{
{
desc: "valid request",
req: updateClientRoleReq{
id: validID,
Role: "admin",
},
err: nil,
},
{
desc: "empty id",
req: updateClientRoleReq{
id: "",
Role: "admin",
},
err: apiutil.ErrMissingID,
},
}
for _, c := range cases {
err := c.req.validate()
assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err)
}
}
func TestUpdateClientIdentityReqValidate(t *testing.T) {
cases := []struct {
desc string
req updateClientIdentityReq
err error
}{
{
desc: "valid request",
req: updateClientIdentityReq{
req: updateUsernameReq{
id: validID,
Identity: "example@example.com",
Username: "validUsername",
},
err: nil,
},
{
desc: "missing user ID",
req: updateUsernameReq{
id: "",
Username: "validUsername",
},
err: apiutil.ErrMissingID,
},
{
desc: "name too long",
req: updateUsernameReq{
id: validID,
Username: strings.Repeat("a", api.MaxNameSize+1),
},
err: apiutil.ErrNameSize,
},
}
for _, tc := range cases {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "%s: expected %s got %s\n", tc.desc, tc.err, err)
}
}
func TestUpdateProfilePictureReqValidate(t *testing.T) {
base64EncodedString := "https://example.com/profile.jpg"
parsedURL, err := url.Parse(base64EncodedString)
if err != nil {
t.Fatalf("Error parsing URL: %v", err)
}
cases := []struct {
desc string
req updateProfilePictureReq
err error
}{
{
desc: "valid request",
req: updateProfilePictureReq{
id: validID,
ProfilePicture: parsedURL.String(),
},
err: nil,
},
{
desc: "empty ID",
req: updateProfilePictureReq{
id: "",
ProfilePicture: parsedURL.String(),
},
err: apiutil.ErrMissingID,
},
}
for _, tc := range cases {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "%s: expected %s got %s\n", tc.desc, tc.err, err)
}
}
func TestUpdateUserRoleReqValidate(t *testing.T) {
cases := []struct {
desc string
req updateUserRoleReq
err error
}{
{
desc: "valid request",
req: updateUserRoleReq{
id: validID,
Role: "admin",
},
err: nil,
},
{
desc: "empty id",
req: updateClientIdentityReq{
id: "",
Identity: "example@example.com",
req: updateUserRoleReq{
id: "",
Role: "admin",
},
err: apiutil.ErrMissingID,
},
@@ -350,15 +401,44 @@ func TestUpdateClientIdentityReqValidate(t *testing.T) {
}
}
func TestUpdateClientSecretReqValidate(t *testing.T) {
func TestUpdateUserEmailReqValidate(t *testing.T) {
cases := []struct {
desc string
req updateClientSecretReq
req updateEmailReq
err error
}{
{
desc: "valid request",
req: updateClientSecretReq{
req: updateEmailReq{
id: validID,
Email: "example@example.com",
},
err: nil,
},
{
desc: "empty id",
req: updateEmailReq{
id: "",
Email: "example@example.com",
},
err: apiutil.ErrMissingID,
},
}
for _, c := range cases {
err := c.req.validate()
assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err)
}
}
func TestUpdateUserSecretReqValidate(t *testing.T) {
cases := []struct {
desc string
req updateUserSecretReq
err error
}{
{
desc: "valid request",
req: updateUserSecretReq{
OldSecret: secret,
NewSecret: secret,
},
@@ -366,7 +446,7 @@ func TestUpdateClientSecretReqValidate(t *testing.T) {
},
{
desc: "missing old secret",
req: updateClientSecretReq{
req: updateUserSecretReq{
OldSecret: "",
NewSecret: secret,
},
@@ -374,7 +454,7 @@ func TestUpdateClientSecretReqValidate(t *testing.T) {
},
{
desc: "missing new secret",
req: updateClientSecretReq{
req: updateUserSecretReq{
OldSecret: secret,
NewSecret: "",
},
@@ -382,7 +462,7 @@ func TestUpdateClientSecretReqValidate(t *testing.T) {
},
{
desc: "invalid new secret",
req: updateClientSecretReq{
req: updateUserSecretReq{
OldSecret: secret,
NewSecret: "invalid",
},
@@ -395,22 +475,22 @@ func TestUpdateClientSecretReqValidate(t *testing.T) {
}
}
func TestChangeClientStatusReqValidate(t *testing.T) {
func TestChangeUserStatusReqValidate(t *testing.T) {
cases := []struct {
desc string
req changeClientStatusReq
req changeUserStatusReq
err error
}{
{
desc: "valid request",
req: changeClientStatusReq{
req: changeUserStatusReq{
id: validID,
},
err: nil,
},
{
desc: "empty id",
req: changeClientStatusReq{
req: changeUserStatusReq{
id: "",
},
err: apiutil.ErrMissingID,
@@ -422,33 +502,33 @@ func TestChangeClientStatusReqValidate(t *testing.T) {
}
}
func TestLoginClientReqValidate(t *testing.T) {
func TestLoginUserReqValidate(t *testing.T) {
cases := []struct {
desc string
req loginClientReq
req loginUserReq
err error
}{
{
desc: "valid request",
req: loginClientReq{
Identity: "eaxmple,example.com",
desc: "valid request with username",
req: loginUserReq{
Username: "example",
Secret: secret,
},
err: nil,
},
{
desc: "empty identity",
req: loginClientReq{
Identity: "",
desc: "empty Username",
req: loginUserReq{
Username: "",
Secret: secret,
},
err: apiutil.ErrMissingIdentity,
err: apiutil.ErrMissingUsername,
},
{
desc: "empty secret",
req: loginClientReq{
Identity: "eaxmple,example.com",
req: loginUserReq{
Secret: "",
Username: "example",
},
err: apiutil.ErrMissingPass,
},
+38 -38
View File
@@ -8,7 +8,7 @@ import (
"net/http"
"github.com/absmach/magistrala"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/users"
)
// MailSent message response when link is sent.
@@ -16,18 +16,18 @@ const MailSent = "Email with reset link is sent"
var (
_ magistrala.Response = (*tokenRes)(nil)
_ magistrala.Response = (*viewClientRes)(nil)
_ magistrala.Response = (*createClientRes)(nil)
_ magistrala.Response = (*changeClientStatusClientRes)(nil)
_ magistrala.Response = (*clientsPageRes)(nil)
_ magistrala.Response = (*viewUserRes)(nil)
_ magistrala.Response = (*createUserRes)(nil)
_ magistrala.Response = (*changeUserStatusRes)(nil)
_ magistrala.Response = (*usersPageRes)(nil)
_ magistrala.Response = (*viewMembersRes)(nil)
_ magistrala.Response = (*passwResetReqRes)(nil)
_ magistrala.Response = (*passwChangeRes)(nil)
_ magistrala.Response = (*assignUsersRes)(nil)
_ magistrala.Response = (*unassignUsersRes)(nil)
_ magistrala.Response = (*updateClientRes)(nil)
_ magistrala.Response = (*updateUserRes)(nil)
_ magistrala.Response = (*tokenRes)(nil)
_ magistrala.Response = (*deleteClientRes)(nil)
_ magistrala.Response = (*deleteUserRes)(nil)
)
type pageRes struct {
@@ -36,12 +36,12 @@ type pageRes struct {
Total uint64 `json:"total"`
}
type createClientRes struct {
mgclients.Client `json:",inline"`
created bool
type createUserRes struct {
users.User
created bool
}
func (res createClientRes) Code() int {
func (res createUserRes) Code() int {
if res.created {
return http.StatusCreated
}
@@ -49,7 +49,7 @@ func (res createClientRes) Code() int {
return http.StatusOK
}
func (res createClientRes) Headers() map[string]string {
func (res createUserRes) Headers() map[string]string {
if res.created {
return map[string]string{
"Location": fmt.Sprintf("/users/%s", res.ID),
@@ -59,7 +59,7 @@ func (res createClientRes) Headers() map[string]string {
return map[string]string{}
}
func (res createClientRes) Empty() bool {
func (res createUserRes) Empty() bool {
return false
}
@@ -81,57 +81,57 @@ func (res tokenRes) Empty() bool {
return res.AccessToken == "" || res.RefreshToken == ""
}
type updateClientRes struct {
mgclients.Client `json:",inline"`
type updateUserRes struct {
users.User `json:",inline"`
}
func (res updateClientRes) Code() int {
func (res updateUserRes) Code() int {
return http.StatusOK
}
func (res updateClientRes) Headers() map[string]string {
func (res updateUserRes) Headers() map[string]string {
return map[string]string{}
}
func (res updateClientRes) Empty() bool {
func (res updateUserRes) Empty() bool {
return false
}
type viewClientRes struct {
mgclients.Client `json:",inline"`
type viewUserRes struct {
users.User `json:",inline"`
}
func (res viewClientRes) Code() int {
func (res viewUserRes) Code() int {
return http.StatusOK
}
func (res viewClientRes) Headers() map[string]string {
func (res viewUserRes) Headers() map[string]string {
return map[string]string{}
}
func (res viewClientRes) Empty() bool {
func (res viewUserRes) Empty() bool {
return false
}
type clientsPageRes struct {
type usersPageRes struct {
pageRes
Clients []viewClientRes `json:"users"`
Users []viewUserRes `json:"users"`
}
func (res clientsPageRes) Code() int {
func (res usersPageRes) Code() int {
return http.StatusOK
}
func (res clientsPageRes) Headers() map[string]string {
func (res usersPageRes) Headers() map[string]string {
return map[string]string{}
}
func (res clientsPageRes) Empty() bool {
func (res usersPageRes) Empty() bool {
return false
}
type viewMembersRes struct {
mgclients.Client `json:",inline"`
users.User `json:",inline"`
}
func (res viewMembersRes) Code() int {
@@ -146,19 +146,19 @@ func (res viewMembersRes) Empty() bool {
return false
}
type changeClientStatusClientRes struct {
mgclients.Client `json:",inline"`
type changeUserStatusRes struct {
users.User `json:",inline"`
}
func (res changeClientStatusClientRes) Code() int {
func (res changeUserStatusRes) Code() int {
return http.StatusOK
}
func (res changeClientStatusClientRes) Headers() map[string]string {
func (res changeUserStatusRes) Headers() map[string]string {
return map[string]string{}
}
func (res changeClientStatusClientRes) Empty() bool {
func (res changeUserStatusRes) Empty() bool {
return false
}
@@ -220,11 +220,11 @@ func (res unassignUsersRes) Empty() bool {
return true
}
type deleteClientRes struct {
type deleteUserRes struct {
deleted bool
}
func (res deleteClientRes) Code() int {
func (res deleteUserRes) Code() int {
if res.deleted {
return http.StatusNoContent
}
@@ -232,10 +232,10 @@ func (res deleteClientRes) Code() int {
return http.StatusOK
}
func (res deleteClientRes) Headers() map[string]string {
func (res deleteUserRes) Headers() map[string]string {
return map[string]string{}
}
func (res deleteClientRes) Empty() bool {
func (res deleteUserRes) Empty() bool {
return true
}
+1 -1
View File
@@ -19,7 +19,7 @@ import (
// MakeHandler returns a HTTP handler for Users and Groups API endpoints.
func MakeHandler(cls users.Service, authn mgauthn.Authentication, tokenClient magistrala.TokenServiceClient, selfRegister bool, grps groups.Service, mux *chi.Mux, logger *slog.Logger, instanceID string, pr *regexp.Regexp, providers ...oauth2.Provider) http.Handler {
clientsHandler(cls, authn, tokenClient, selfRegister, mux, logger, pr, providers...)
usersHandler(cls, authn, tokenClient, selfRegister, mux, logger, pr, providers...)
groupsHandler(grps, authn, mux, logger)
mux.Get("/health", magistrala.Health("users", instanceID))
+181 -117
View File
@@ -16,7 +16,6 @@ import (
"github.com/absmach/magistrala/internal/api"
"github.com/absmach/magistrala/pkg/apiutil"
mgauthn "github.com/absmach/magistrala/pkg/authn"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/magistrala/pkg/oauth2"
"github.com/absmach/magistrala/pkg/policies"
@@ -28,8 +27,8 @@ import (
var passRegex = regexp.MustCompile("^.{8,}$")
// MakeHandler returns a HTTP handler for API endpoints.
func clientsHandler(svc users.Service, authn mgauthn.Authentication, tokenClient magistrala.TokenServiceClient, selfRegister bool, r *chi.Mux, logger *slog.Logger, pr *regexp.Regexp, providers ...oauth2.Provider) http.Handler {
// usersHandler returns a HTTP handler for API endpoints.
func usersHandler(svc users.Service, authn mgauthn.Authentication, tokenClient magistrala.TokenServiceClient, selfRegister bool, r *chi.Mux, logger *slog.Logger, pr *regexp.Regexp, providers ...oauth2.Provider) http.Handler {
passRegex = pr
opts := []kithttp.ServerOption{
@@ -41,17 +40,17 @@ func clientsHandler(svc users.Service, authn mgauthn.Authentication, tokenClient
case true:
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
registrationEndpoint(svc, selfRegister),
decodeCreateClientReq,
decodeCreateUserReq,
api.EncodeResponse,
opts...,
), "register_client").ServeHTTP)
), "register_user").ServeHTTP)
default:
r.With(api.AuthenticateMiddleware(authn, false)).Post("/", otelhttp.NewHandler(kithttp.NewServer(
registrationEndpoint(svc, selfRegister),
decodeCreateClientReq,
decodeCreateUserReq,
api.EncodeResponse,
opts...,
), "register_client").ServeHTTP)
), "register_user").ServeHTTP)
}
r.Group(func(r chi.Router) {
@@ -65,88 +64,95 @@ func clientsHandler(svc users.Service, authn mgauthn.Authentication, tokenClient
), "view_profile").ServeHTTP)
r.Get("/{id}", otelhttp.NewHandler(kithttp.NewServer(
viewClientEndpoint(svc),
decodeViewClient,
viewEndpoint(svc),
decodeViewUser,
api.EncodeResponse,
opts...,
), "view_client").ServeHTTP)
), "view_user").ServeHTTP)
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
listClientsEndpoint(svc),
decodeListClients,
listUsersEndpoint(svc),
decodeListUsers,
api.EncodeResponse,
opts...,
), "list_clients").ServeHTTP)
), "list_users").ServeHTTP)
r.Get("/search", otelhttp.NewHandler(kithttp.NewServer(
searchClientsEndpoint(svc),
decodeSearchClients,
searchUsersEndpoint(svc),
decodeSearchUsers,
api.EncodeResponse,
opts...,
), "search_clients").ServeHTTP)
), "search_users").ServeHTTP)
r.Patch("/secret", otelhttp.NewHandler(kithttp.NewServer(
updateClientSecretEndpoint(svc),
decodeUpdateClientSecret,
updateSecretEndpoint(svc),
decodeUpdateUserSecret,
api.EncodeResponse,
opts...,
), "update_client_secret").ServeHTTP)
), "update_user_secret").ServeHTTP)
r.Patch("/{id}", otelhttp.NewHandler(kithttp.NewServer(
updateClientEndpoint(svc),
decodeUpdateClient,
updateEndpoint(svc),
decodeUpdateUser,
api.EncodeResponse,
opts...,
), "update_client").ServeHTTP)
), "update_user").ServeHTTP)
r.Patch("/{id}/username", otelhttp.NewHandler(kithttp.NewServer(
updateUsernameEndpoint(svc),
decodeUpdateUsername,
api.EncodeResponse,
opts...,
), "update_username").ServeHTTP)
r.Patch("/{id}/picture", otelhttp.NewHandler(kithttp.NewServer(
updateProfilePictureEndpoint(svc),
decodeUpdateUserProfilePicture,
api.EncodeResponse,
opts...,
), "update_profile_picture").ServeHTTP)
r.Patch("/{id}/tags", otelhttp.NewHandler(kithttp.NewServer(
updateClientTagsEndpoint(svc),
decodeUpdateClientTags,
updateTagsEndpoint(svc),
decodeUpdateUserTags,
api.EncodeResponse,
opts...,
), "update_client_tags").ServeHTTP)
), "update_user_tags").ServeHTTP)
r.Patch("/{id}/identity", otelhttp.NewHandler(kithttp.NewServer(
updateClientIdentityEndpoint(svc),
decodeUpdateClientIdentity,
r.Patch("/{id}/email", otelhttp.NewHandler(kithttp.NewServer(
updateEmailEndpoint(svc),
decodeUpdateUserEmail,
api.EncodeResponse,
opts...,
), "update_client_identity").ServeHTTP)
), "update_user_email").ServeHTTP)
r.Patch("/{id}/role", otelhttp.NewHandler(kithttp.NewServer(
updateClientRoleEndpoint(svc),
decodeUpdateClientRole,
updateRoleEndpoint(svc),
decodeUpdateUserRole,
api.EncodeResponse,
opts...,
), "update_client_role").ServeHTTP)
r.Patch("/{id}/role", otelhttp.NewHandler(kithttp.NewServer(
updateClientRoleEndpoint(svc),
decodeUpdateClientRole,
api.EncodeResponse,
opts...,
), "update_client_role").ServeHTTP)
), "update_user_role").ServeHTTP)
r.Post("/{id}/enable", otelhttp.NewHandler(kithttp.NewServer(
enableClientEndpoint(svc),
decodeChangeClientStatus,
enableEndpoint(svc),
decodeChangeUserStatus,
api.EncodeResponse,
opts...,
), "enable_client").ServeHTTP)
), "enable_user").ServeHTTP)
r.Post("/{id}/disable", otelhttp.NewHandler(kithttp.NewServer(
disableClientEndpoint(svc),
decodeChangeClientStatus,
disableEndpoint(svc),
decodeChangeUserStatus,
api.EncodeResponse,
opts...,
), "disable_client").ServeHTTP)
), "disable_user").ServeHTTP)
r.Delete("/{id}", otelhttp.NewHandler(kithttp.NewServer(
deleteClientEndpoint(svc),
decodeChangeClientStatus,
deleteEndpoint(svc),
decodeChangeUserStatus,
api.EncodeResponse,
opts...,
), "delete_client").ServeHTTP)
), "delete_user").ServeHTTP)
r.Post("/tokens/refresh", otelhttp.NewHandler(kithttp.NewServer(
refreshTokenEndpoint(svc),
@@ -230,8 +236,8 @@ func clientsHandler(svc users.Service, authn mgauthn.Authentication, tokenClient
return r
}
func decodeViewClient(_ context.Context, r *http.Request) (interface{}, error) {
req := viewClientReq{
func decodeViewUser(_ context.Context, r *http.Request) (interface{}, error) {
req := viewUserReq{
id: chi.URLParam(r, "id"),
}
@@ -242,7 +248,7 @@ func decodeViewProfile(_ context.Context, r *http.Request) (interface{}, error)
return nil, nil
}
func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) {
func decodeListUsers(_ context.Context, r *http.Request) (interface{}, error) {
s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
@@ -259,11 +265,19 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
n, err := apiutil.ReadStringQuery(r, api.NameKey, "")
n, err := apiutil.ReadStringQuery(r, api.UsernameKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
i, err := apiutil.ReadStringQuery(r, api.IdentityKey, "")
d, err := apiutil.ReadStringQuery(r, api.EmailKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
i, err := apiutil.ReadStringQuery(r, api.FirstNameKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
f, err := apiutil.ReadStringQuery(r, api.LastNameKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -284,27 +298,29 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error)
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
st, err := mgclients.ToStatus(s)
st, err := users.ToStatus(s)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
req := listClientsReq{
status: st,
offset: o,
limit: l,
metadata: m,
name: n,
identity: i,
tag: t,
order: order,
dir: dir,
id: id,
req := listUsersReq{
status: st,
offset: o,
limit: l,
metadata: m,
userName: n,
firstName: i,
lastName: f,
tag: t,
order: order,
dir: dir,
id: id,
email: d,
}
return req, nil
}
func decodeSearchClients(_ context.Context, r *http.Request) (interface{}, error) {
func decodeSearchUsers(_ context.Context, r *http.Request) (interface{}, error) {
o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
@@ -313,7 +329,15 @@ func decodeSearchClients(_ context.Context, r *http.Request) (interface{}, error
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
n, err := apiutil.ReadStringQuery(r, api.NameKey, "")
n, err := apiutil.ReadStringQuery(r, api.UsernameKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
f, err := apiutil.ReadStringQuery(r, api.FirstNameKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
e, err := apiutil.ReadStringQuery(r, api.LastNameKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
@@ -330,18 +354,20 @@ func decodeSearchClients(_ context.Context, r *http.Request) (interface{}, error
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
req := searchClientsReq{
Offset: o,
Limit: l,
Name: n,
Id: id,
Order: order,
Dir: dir,
req := searchUsersReq{
Offset: o,
Limit: l,
Username: n,
FirstName: f,
LastName: e,
Id: id,
Order: order,
Dir: dir,
}
for _, field := range []string{req.Name, req.Id} {
for _, field := range []string{req.Username, req.Id} {
if field != "" && len(field) < 3 {
req = searchClientsReq{}
req = searchUsersReq{}
return req, errors.Wrap(apiutil.ErrLenSearchQuery, apiutil.ErrValidation)
}
}
@@ -349,12 +375,12 @@ func decodeSearchClients(_ context.Context, r *http.Request) (interface{}, error
return req, nil
}
func decodeUpdateClient(_ context.Context, r *http.Request) (interface{}, error) {
func decodeUpdateUser(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
req := updateClientReq{
req := updateUserReq{
id: chi.URLParam(r, "id"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -364,12 +390,12 @@ func decodeUpdateClient(_ context.Context, r *http.Request) (interface{}, error)
return req, nil
}
func decodeUpdateClientTags(_ context.Context, r *http.Request) (interface{}, error) {
func decodeUpdateUserTags(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
req := updateClientTagsReq{
req := updateUserTagsReq{
id: chi.URLParam(r, "id"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -379,12 +405,12 @@ func decodeUpdateClientTags(_ context.Context, r *http.Request) (interface{}, er
return req, nil
}
func decodeUpdateClientIdentity(_ context.Context, r *http.Request) (interface{}, error) {
func decodeUpdateUserEmail(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
req := updateClientIdentityReq{
req := updateEmailReq{
id: chi.URLParam(r, "id"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -394,12 +420,43 @@ func decodeUpdateClientIdentity(_ context.Context, r *http.Request) (interface{}
return req, nil
}
func decodeUpdateClientSecret(_ context.Context, r *http.Request) (interface{}, error) {
func decodeUpdateUserSecret(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
req := updateClientSecretReq{}
req := updateUserSecretReq{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
}
return req, nil
}
func decodeUpdateUsername(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
req := updateUsernameReq{
id: chi.URLParam(r, "id"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
}
return req, nil
}
func decodeUpdateUserProfilePicture(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
req := updateProfilePictureReq{
id: chi.URLParam(r, "id"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
}
@@ -434,19 +491,19 @@ func decodePasswordReset(_ context.Context, r *http.Request) (interface{}, error
return req, nil
}
func decodeUpdateClientRole(_ context.Context, r *http.Request) (interface{}, error) {
func decodeUpdateUserRole(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
req := updateClientRoleReq{
req := updateUserRoleReq{
id: chi.URLParam(r, "id"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
}
var err error
req.role, err = mgclients.ToRole(req.Role)
req.role, err = users.ToRole(req.Role)
return req, err
}
@@ -455,7 +512,7 @@ func decodeCredentials(_ context.Context, r *http.Request) (interface{}, error)
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
req := loginClientReq{}
req := loginUserReq{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
}
@@ -472,24 +529,21 @@ func decodeRefreshToken(_ context.Context, r *http.Request) (interface{}, error)
return req, nil
}
func decodeCreateClientReq(_ context.Context, r *http.Request) (interface{}, error) {
func decodeCreateUserReq(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
var c mgclients.Client
if err := json.NewDecoder(r.Body).Decode(&c); err != nil {
var req createUserReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
}
req := createClientReq{
client: c,
}
return req, nil
}
func decodeChangeClientStatus(_ context.Context, r *http.Request) (interface{}, error) {
req := changeClientStatusReq{
func decodeChangeUserStatus(_ context.Context, r *http.Request) (interface{}, error) {
req := changeUserStatusReq{
id: chi.URLParam(r, "id"),
}
@@ -549,54 +603,64 @@ func decodeListMembersByDomain(_ context.Context, r *http.Request) (interface{},
return req, nil
}
func queryPageParams(r *http.Request, defPermission string) (mgclients.Page, error) {
func queryPageParams(r *http.Request, defPermission string) (users.Page, error) {
s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus)
if err != nil {
return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
if err != nil {
return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit)
if err != nil {
return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
m, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil)
if err != nil {
return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
n, err := apiutil.ReadStringQuery(r, api.NameKey, "")
n, err := apiutil.ReadStringQuery(r, api.UsernameKey, "")
if err != nil {
return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
i, err := apiutil.ReadStringQuery(r, api.IdentityKey, "")
f, err := apiutil.ReadStringQuery(r, api.FirstNameKey, "")
if err != nil {
return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
a, err := apiutil.ReadStringQuery(r, api.LastNameKey, "")
if err != nil {
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
i, err := apiutil.ReadStringQuery(r, api.EmailKey, "")
if err != nil {
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
t, err := apiutil.ReadStringQuery(r, api.TagKey, "")
if err != nil {
return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
st, err := mgclients.ToStatus(s)
st, err := users.ToStatus(s)
if err != nil {
return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
p, err := apiutil.ReadStringQuery(r, api.PermissionKey, defPermission)
if err != nil {
return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
lp, err := apiutil.ReadBoolQuery(r, api.ListPerms, api.DefListPerms)
if err != nil {
return mgclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
return users.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
return mgclients.Page{
return users.Page{
Status: st,
Offset: o,
Limit: l,
Metadata: m,
Identity: i,
Name: n,
FirstName: f,
Username: n,
LastName: a,
Email: i,
Tag: t,
Permission: p,
ListPerms: lp,
@@ -623,24 +687,24 @@ func oauth2CallbackHandler(oauth oauth2.Provider, svc users.Service, tokenClient
return
}
client, err := oauth.UserInfo(token.AccessToken)
user, err := oauth.UserInfo(token.AccessToken)
if err != nil {
http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther)
return
}
client, err = svc.OAuthCallback(r.Context(), client)
user, err = svc.OAuthCallback(r.Context(), user)
if err != nil {
http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther)
return
}
if err := svc.OAuthAddClientPolicy(r.Context(), client); err != nil {
if err := svc.OAuthAddUserPolicy(r.Context(), user); err != nil {
http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther)
return
}
jwt, err := tokenClient.Issue(r.Context(), &magistrala.IssueReq{
UserId: client.ID,
UserId: user.ID,
Type: uint32(mgauth.AccessKey),
})
if err != nil {
-90
View File
@@ -1,90 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package users
import (
"context"
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/pkg/authn"
"github.com/absmach/magistrala/pkg/clients"
)
// Service specifies an API that must be fullfiled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
//
//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines"
type Service interface {
// RegisterClient creates new client. In case of the failed registration, a
// non-nil error value is returned.
RegisterClient(ctx context.Context, session authn.Session, client clients.Client, selfRegister bool) (clients.Client, error)
// ViewClient retrieves client info for a given client ID and an authorized token.
ViewClient(ctx context.Context, session authn.Session, id string) (clients.Client, error)
// ViewProfile retrieves client info for a given token.
ViewProfile(ctx context.Context, session authn.Session) (clients.Client, error)
// ListClients retrieves clients list for a valid auth token.
ListClients(ctx context.Context, session authn.Session, pm clients.Page) (clients.ClientsPage, error)
// ListMembers retrieves everything that is assigned to a group/thing identified by objectID.
ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm clients.Page) (clients.MembersPage, error)
// SearchClients searches for users with provided filters for a valid auth token.
SearchUsers(ctx context.Context, pm clients.Page) (clients.ClientsPage, error)
// UpdateClient updates the client's name and metadata.
UpdateClient(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error)
// UpdateClientTags updates the client's tags.
UpdateClientTags(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error)
// UpdateClientIdentity updates the client's identity.
UpdateClientIdentity(ctx context.Context, session authn.Session, id, identity string) (clients.Client, error)
// GenerateResetToken email where mail will be sent.
// host is used for generating reset link.
GenerateResetToken(ctx context.Context, email, host string) error
// UpdateClientSecret updates the client's secret.
UpdateClientSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (clients.Client, error)
// ResetSecret change users secret in reset flow.
// token can be authentication token or secret reset token.
ResetSecret(ctx context.Context, session authn.Session, secret string) error
// SendPasswordReset sends reset password link to email.
SendPasswordReset(ctx context.Context, host, email, user, token string) error
// UpdateClientRole updates the client's Role.
UpdateClientRole(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error)
// EnableClient logically enableds the client identified with the provided ID.
EnableClient(ctx context.Context, session authn.Session, id string) (clients.Client, error)
// DisableClient logically disables the client identified with the provided ID.
DisableClient(ctx context.Context, session authn.Session, id string) (clients.Client, error)
// DeleteClient deletes client with given ID.
DeleteClient(ctx context.Context, session authn.Session, id string) error
// Identify returns the client id from the given token.
Identify(ctx context.Context, session authn.Session) (string, error)
// IssueToken issues a new access and refresh token.
IssueToken(ctx context.Context, identity, secret string) (*magistrala.Token, error)
// RefreshToken refreshes expired access tokens.
// After an access token expires, the refresh token is used to get
// a new pair of access and refresh tokens.
RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error)
// OAuthCallback handles the callback from any supported OAuth provider.
// It processes the OAuth tokens and either signs in or signs up the user based on the provided state.
OAuthCallback(ctx context.Context, client clients.Client) (clients.Client, error)
// OAuthAddClientPolicy adds a policy to the client for an OAuth request.
OAuthAddClientPolicy(ctx context.Context, client clients.Client) error
}
+9 -10
View File
@@ -15,16 +15,14 @@ import (
"time"
"github.com/absmach/magistrala"
mgclients "github.com/absmach/magistrala/pkg/clients"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
"github.com/absmach/magistrala/pkg/policies"
"github.com/absmach/magistrala/users/postgres"
)
const defLimit = uint64(100)
type handler struct {
clients postgres.Repository
users Repository
domains magistrala.DomainsServiceClient
policies policies.Service
checkInterval time.Duration
@@ -32,9 +30,9 @@ type handler struct {
logger *slog.Logger
}
func NewDeleteHandler(ctx context.Context, clients postgres.Repository, policyService policies.Service, domainsClient magistrala.DomainsServiceClient, defCheckInterval, deleteAfter time.Duration, logger *slog.Logger) {
func NewDeleteHandler(ctx context.Context, users Repository, policyService policies.Service, domainsClient magistrala.DomainsServiceClient, defCheckInterval, deleteAfter time.Duration, logger *slog.Logger) {
handler := &handler{
clients: clients,
users: users,
domains: domainsClient,
policies: policyService,
checkInterval: defCheckInterval,
@@ -58,10 +56,10 @@ func NewDeleteHandler(ctx context.Context, clients postgres.Repository, policySe
}
func (h *handler) handle(ctx context.Context) {
pm := mgclients.Page{Limit: defLimit, Offset: 0, Status: mgclients.DeletedStatus}
pm := Page{Limit: defLimit, Offset: 0, Status: DeletedStatus}
for {
dbUsers, err := h.clients.RetrieveAll(ctx, pm)
dbUsers, err := h.users.RetrieveAll(ctx, pm)
if err != nil {
h.logger.Error("failed to retrieve users", slog.Any("error", err))
break
@@ -70,7 +68,7 @@ func (h *handler) handle(ctx context.Context) {
break
}
for _, u := range dbUsers.Clients {
for _, u := range dbUsers.Users {
if time.Since(u.UpdatedAt) < h.deleteAfter {
continue
}
@@ -96,14 +94,15 @@ func (h *handler) handle(ctx context.Context) {
continue
}
if err := h.clients.Delete(ctx, u.ID); err != nil {
if err := h.users.Delete(ctx, u.ID); err != nil {
h.logger.Error("failed to delete user", slog.Any("error", err))
continue
}
h.logger.Info("user deleted", slog.Group("user",
slog.String("id", u.ID),
slog.String("name", u.Name),
slog.String("first_name", u.FirstName),
slog.String("last_name", u.LastName),
))
}
}
+225 -142
View File
@@ -6,107 +6,121 @@ package events
import (
"time"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/pkg/events"
"github.com/absmach/magistrala/users"
)
const (
clientPrefix = "user."
clientCreate = clientPrefix + "create"
clientUpdate = clientPrefix + "update"
clientRemove = clientPrefix + "remove"
clientView = clientPrefix + "view"
profileView = clientPrefix + "view_profile"
clientList = clientPrefix + "list"
clientSearch = clientPrefix + "search"
clientListByGroup = clientPrefix + "list_by_group"
clientIdentify = clientPrefix + "identify"
generateResetToken = clientPrefix + "generate_reset_token"
issueToken = clientPrefix + "issue_token"
refreshToken = clientPrefix + "refresh_token"
resetSecret = clientPrefix + "reset_secret"
sendPasswordReset = clientPrefix + "send_password_reset"
oauthCallback = clientPrefix + "oauth_callback"
deleteClient = clientPrefix + "delete"
addClientPolicy = clientPrefix + "add_policy"
userPrefix = "user."
userCreate = userPrefix + "create"
userUpdate = userPrefix + "update"
userRemove = userPrefix + "remove"
userView = userPrefix + "view"
profileView = userPrefix + "view_profile"
userList = userPrefix + "list"
userSearch = userPrefix + "search"
userListByGroup = userPrefix + "list_by_group"
userIdentify = userPrefix + "identify"
generateResetToken = userPrefix + "generate_reset_token"
issueToken = userPrefix + "issue_token"
refreshToken = userPrefix + "refresh_token"
resetSecret = userPrefix + "reset_secret"
sendPasswordReset = userPrefix + "send_password_reset"
oauthCallback = userPrefix + "oauth_callback"
addClientPolicy = userPrefix + "add_policy"
deleteUser = userPrefix + "delete"
userUpdateUsername = userPrefix + "update_username"
userUpdateProfilePicture = userPrefix + "update_profile_picture"
)
var (
_ events.Event = (*createClientEvent)(nil)
_ events.Event = (*updateClientEvent)(nil)
_ events.Event = (*removeClientEvent)(nil)
_ events.Event = (*viewClientEvent)(nil)
_ events.Event = (*createUserEvent)(nil)
_ events.Event = (*updateUserEvent)(nil)
_ events.Event = (*updateProfilePictureEvent)(nil)
_ events.Event = (*updateUsernameEvent)(nil)
_ events.Event = (*removeUserEvent)(nil)
_ events.Event = (*viewUserEvent)(nil)
_ events.Event = (*viewProfileEvent)(nil)
_ events.Event = (*listClientEvent)(nil)
_ events.Event = (*listClientByGroupEvent)(nil)
_ events.Event = (*searchClientEvent)(nil)
_ events.Event = (*identifyClientEvent)(nil)
_ events.Event = (*listUserEvent)(nil)
_ events.Event = (*listUserByGroupEvent)(nil)
_ events.Event = (*searchUserEvent)(nil)
_ events.Event = (*identifyUserEvent)(nil)
_ events.Event = (*generateResetTokenEvent)(nil)
_ events.Event = (*issueTokenEvent)(nil)
_ events.Event = (*refreshTokenEvent)(nil)
_ events.Event = (*resetSecretEvent)(nil)
_ events.Event = (*sendPasswordResetEvent)(nil)
_ events.Event = (*oauthCallbackEvent)(nil)
_ events.Event = (*deleteClientEvent)(nil)
_ events.Event = (*deleteUserEvent)(nil)
_ events.Event = (*addUserPolicyEvent)(nil)
)
type createClientEvent struct {
mgclients.Client
type createUserEvent struct {
users.User
}
func (cce createClientEvent) Encode() (map[string]interface{}, error) {
func (uce createUserEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": clientCreate,
"id": cce.ID,
"status": cce.Status.String(),
"created_at": cce.CreatedAt,
"operation": userCreate,
"id": uce.ID,
"status": uce.Status.String(),
"created_at": uce.CreatedAt,
}
if cce.Name != "" {
val["name"] = cce.Name
if uce.FirstName != "" {
val["first_name"] = uce.FirstName
}
if len(cce.Tags) > 0 {
val["tags"] = cce.Tags
if uce.LastName != "" {
val["last_name"] = uce.LastName
}
if cce.Domain != "" {
val["domain"] = cce.Domain
if len(uce.Tags) > 0 {
val["tags"] = uce.Tags
}
if cce.Metadata != nil {
val["metadata"] = cce.Metadata
if uce.Metadata != nil {
val["metadata"] = uce.Metadata
}
if cce.Credentials.Identity != "" {
val["identity"] = cce.Credentials.Identity
if uce.Credentials.Username != "" {
val["username"] = uce.Credentials.Username
}
if uce.Email != "" {
val["email"] = uce.Email
}
return val, nil
}
type updateClientEvent struct {
mgclients.Client
type updateUserEvent struct {
users.User
operation string
}
func (uce updateClientEvent) Encode() (map[string]interface{}, error) {
func (uce updateUserEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": clientUpdate,
"operation": userUpdate,
"updated_at": uce.UpdatedAt,
"updated_by": uce.UpdatedBy,
}
if uce.operation != "" {
val["operation"] = clientUpdate + "_" + uce.operation
val["operation"] = userUpdate + "_" + uce.operation
}
if uce.ID != "" {
val["id"] = uce.ID
}
if uce.Name != "" {
val["name"] = uce.Name
if uce.FirstName != "" {
val["first_name"] = uce.FirstName
}
if uce.LastName != "" {
val["last_name"] = uce.LastName
}
if len(uce.Tags) > 0 {
val["tags"] = uce.Tags
}
if uce.Credentials.Identity != "" {
val["identity"] = uce.Credentials.Identity
if uce.Credentials.Username != "" {
val["username"] = uce.Credentials.Username
}
if uce.Email != "" {
val["email"] = uce.Email
}
if uce.Metadata != nil {
val["metadata"] = uce.Metadata
@@ -121,16 +135,64 @@ func (uce updateClientEvent) Encode() (map[string]interface{}, error) {
return val, nil
}
type removeClientEvent struct {
type updateUsernameEvent struct {
users.User
}
func (une updateUsernameEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": userUpdateUsername,
"updated_at": une.UpdatedAt,
"updated_by": une.UpdatedBy,
}
if une.ID != "" {
val["id"] = une.ID
}
if une.FirstName != "" {
val["first_name"] = une.FirstName
}
if une.LastName != "" {
val["last_name"] = une.LastName
}
if une.Credentials.Username != "" {
val["username"] = une.Credentials.Username
}
return val, nil
}
type updateProfilePictureEvent struct {
users.User
}
func (uppe updateProfilePictureEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": userUpdateProfilePicture,
"updated_at": uppe.UpdatedAt,
"updated_by": uppe.UpdatedBy,
}
if uppe.ID != "" {
val["id"] = uppe.ID
}
if uppe.ProfilePicture != "" {
val["profile_picture"] = uppe.ProfilePicture
}
return val, nil
}
type removeUserEvent struct {
id string
status string
updatedAt time.Time
updatedBy string
}
func (rce removeClientEvent) Encode() (map[string]interface{}, error) {
func (rce removeUserEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
"operation": clientRemove,
"operation": userRemove,
"id": rce.id,
"status": rce.status,
"updated_at": rce.updatedAt,
@@ -138,49 +200,52 @@ func (rce removeClientEvent) Encode() (map[string]interface{}, error) {
}, nil
}
type viewClientEvent struct {
mgclients.Client
type viewUserEvent struct {
users.User
}
func (vce viewClientEvent) Encode() (map[string]interface{}, error) {
func (vue viewUserEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": clientView,
"id": vce.ID,
"operation": userView,
"id": vue.ID,
}
if vce.Name != "" {
val["name"] = vce.Name
if vue.LastName != "" {
val["last_name"] = vue.LastName
}
if len(vce.Tags) > 0 {
val["tags"] = vce.Tags
if vue.FirstName != "" {
val["first_name"] = vue.FirstName
}
if vce.Domain != "" {
val["domain"] = vce.Domain
if len(vue.Tags) > 0 {
val["tags"] = vue.Tags
}
if vce.Credentials.Identity != "" {
val["identity"] = vce.Credentials.Identity
if vue.Email != "" {
val["email"] = vue.Email
}
if vce.Metadata != nil {
val["metadata"] = vce.Metadata
if vue.Credentials.Username != "" {
val["email"] = vue.Credentials.Username
}
if !vce.CreatedAt.IsZero() {
val["created_at"] = vce.CreatedAt
if vue.Metadata != nil {
val["metadata"] = vue.Metadata
}
if !vce.UpdatedAt.IsZero() {
val["updated_at"] = vce.UpdatedAt
if !vue.CreatedAt.IsZero() {
val["created_at"] = vue.CreatedAt
}
if vce.UpdatedBy != "" {
val["updated_by"] = vce.UpdatedBy
if !vue.UpdatedAt.IsZero() {
val["updated_at"] = vue.UpdatedAt
}
if vce.Status.String() != "" {
val["status"] = vce.Status.String()
if vue.UpdatedBy != "" {
val["updated_by"] = vue.UpdatedBy
}
if vue.Status.String() != "" {
val["status"] = vue.Status.String()
}
return val, nil
}
type viewProfileEvent struct {
mgclients.Client
users.User
}
func (vpe viewProfileEvent) Encode() (map[string]interface{}, error) {
@@ -189,17 +254,14 @@ func (vpe viewProfileEvent) Encode() (map[string]interface{}, error) {
"id": vpe.ID,
}
if vpe.Name != "" {
val["name"] = vpe.Name
if vpe.FirstName != "" {
val["first_name"] = vpe.FirstName
}
if len(vpe.Tags) > 0 {
val["tags"] = vpe.Tags
}
if vpe.Domain != "" {
val["domain"] = vpe.Domain
}
if vpe.Credentials.Identity != "" {
val["identity"] = vpe.Credentials.Identity
if vpe.Credentials.Username != "" {
val["username"] = vpe.Credentials.Username
}
if vpe.Metadata != nil {
val["metadata"] = vpe.Metadata
@@ -216,62 +278,71 @@ func (vpe viewProfileEvent) Encode() (map[string]interface{}, error) {
if vpe.Status.String() != "" {
val["status"] = vpe.Status.String()
}
if vpe.Email != "" {
val["email"] = vpe.Email
}
return val, nil
}
type listClientEvent struct {
mgclients.Page
type listUserEvent struct {
users.Page
}
func (lce listClientEvent) Encode() (map[string]interface{}, error) {
func (lue listUserEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": clientList,
"total": lce.Total,
"offset": lce.Offset,
"limit": lce.Limit,
"operation": userList,
"total": lue.Total,
"offset": lue.Offset,
"limit": lue.Limit,
}
if lce.Name != "" {
val["name"] = lce.Name
if lue.FirstName != "" {
val["first_name"] = lue.FirstName
}
if lce.Order != "" {
val["order"] = lce.Order
if lue.LastName != "" {
val["last_name"] = lue.LastName
}
if lce.Dir != "" {
val["dir"] = lce.Dir
if lue.Order != "" {
val["order"] = lue.Order
}
if lce.Metadata != nil {
val["metadata"] = lce.Metadata
if lue.Dir != "" {
val["dir"] = lue.Dir
}
if lce.Domain != "" {
val["domain"] = lce.Domain
if lue.Metadata != nil {
val["metadata"] = lue.Metadata
}
if lce.Tag != "" {
val["tag"] = lce.Tag
if lue.Domain != "" {
val["domain"] = lue.Domain
}
if lce.Permission != "" {
val["permission"] = lce.Permission
if lue.Tag != "" {
val["tag"] = lue.Tag
}
if lce.Status.String() != "" {
val["status"] = lce.Status.String()
if lue.Permission != "" {
val["permission"] = lue.Permission
}
if lce.Identity != "" {
val["identity"] = lce.Identity
if lue.Status.String() != "" {
val["status"] = lue.Status.String()
}
if lue.Username != "" {
val["username"] = lue.Username
}
if lue.Email != "" {
val["email"] = lue.Email
}
return val, nil
}
type listClientByGroupEvent struct {
mgclients.Page
type listUserByGroupEvent struct {
users.Page
objectKind string
objectID string
}
func (lcge listClientByGroupEvent) Encode() (map[string]interface{}, error) {
func (lcge listUserByGroupEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": clientListByGroup,
"operation": userListByGroup,
"total": lcge.Total,
"offset": lcge.Offset,
"limit": lcge.Limit,
@@ -279,8 +350,8 @@ func (lcge listClientByGroupEvent) Encode() (map[string]interface{}, error) {
"object_id": lcge.objectID,
}
if lcge.Name != "" {
val["name"] = lcge.Name
if lcge.Username != "" {
val["username"] = lcge.Username
}
if lcge.Order != "" {
val["order"] = lcge.Order
@@ -303,29 +374,41 @@ func (lcge listClientByGroupEvent) Encode() (map[string]interface{}, error) {
if lcge.Status.String() != "" {
val["status"] = lcge.Status.String()
}
if lcge.Identity != "" {
val["identity"] = lcge.Identity
if lcge.FirstName != "" {
val["first_name"] = lcge.FirstName
}
if lcge.LastName != "" {
val["last_name"] = lcge.LastName
}
if lcge.Email != "" {
val["email"] = lcge.Email
}
return val, nil
}
type searchClientEvent struct {
mgclients.Page
type searchUserEvent struct {
users.Page
}
func (sce searchClientEvent) Encode() (map[string]interface{}, error) {
func (sce searchUserEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": clientSearch,
"operation": userSearch,
"total": sce.Total,
"offset": sce.Offset,
"limit": sce.Limit,
}
if sce.Name != "" {
val["name"] = sce.Name
if sce.Username != "" {
val["username"] = sce.Username
}
if sce.Identity != "" {
val["identity"] = sce.Identity
if sce.FirstName != "" {
val["first_name"] = sce.FirstName
}
if sce.LastName != "" {
val["last_name"] = sce.LastName
}
if sce.Email != "" {
val["email"] = sce.Email
}
if sce.Id != "" {
val["id"] = sce.Id
@@ -334,14 +417,14 @@ func (sce searchClientEvent) Encode() (map[string]interface{}, error) {
return val, nil
}
type identifyClientEvent struct {
type identifyUserEvent struct {
userID string
}
func (ice identifyClientEvent) Encode() (map[string]interface{}, error) {
func (ise identifyUserEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
"operation": clientIdentify,
"id": ice.userID,
"operation": userIdentify,
"id": ise.userID,
}, nil
}
@@ -359,13 +442,13 @@ func (grte generateResetTokenEvent) Encode() (map[string]interface{}, error) {
}
type issueTokenEvent struct {
identity string
username string
}
func (ite issueTokenEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
"operation": issueToken,
"identity": ite.identity,
"username": ite.username,
}, nil
}
@@ -401,33 +484,33 @@ func (spre sendPasswordResetEvent) Encode() (map[string]interface{}, error) {
}
type oauthCallbackEvent struct {
clientID string
userID string
}
func (oce oauthCallbackEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
"operation": oauthCallback,
"client_id": oce.clientID,
"user_id": oce.userID,
}, nil
}
type deleteClientEvent struct {
type deleteUserEvent struct {
id string
}
func (dce deleteClientEvent) Encode() (map[string]interface{}, error) {
func (dce deleteUserEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
"operation": deleteClient,
"operation": deleteUser,
"id": dce.id,
}, nil
}
type addClientPolicyEvent struct {
type addUserPolicyEvent struct {
id string
role string
}
func (acpe addClientPolicyEvent) Encode() (map[string]interface{}, error) {
func (acpe addUserPolicyEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
"operation": addClientPolicy,
"id": acpe.id,
+82 -49
View File
@@ -8,7 +8,6 @@ import (
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/pkg/authn"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/pkg/events"
"github.com/absmach/magistrala/pkg/events/store"
"github.com/absmach/magistrala/users"
@@ -37,13 +36,13 @@ func NewEventStoreMiddleware(ctx context.Context, svc users.Service, url string)
}, nil
}
func (es *eventStore) RegisterClient(ctx context.Context, session authn.Session, user mgclients.Client, selfRegister bool) (mgclients.Client, error) {
user, err := es.svc.RegisterClient(ctx, session, user, selfRegister)
func (es *eventStore) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (users.User, error) {
user, err := es.svc.Register(ctx, session, user, selfRegister)
if err != nil {
return user, err
}
event := createClientEvent{
event := createUserEvent{
user,
}
@@ -54,8 +53,8 @@ func (es *eventStore) RegisterClient(ctx context.Context, session authn.Session,
return user, nil
}
func (es *eventStore) UpdateClient(ctx context.Context, session authn.Session, user mgclients.Client) (mgclients.Client, error) {
user, err := es.svc.UpdateClient(ctx, session, user)
func (es *eventStore) Update(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
user, err := es.svc.Update(ctx, session, user)
if err != nil {
return user, err
}
@@ -63,8 +62,8 @@ func (es *eventStore) UpdateClient(ctx context.Context, session authn.Session, u
return es.update(ctx, "", user)
}
func (es *eventStore) UpdateClientRole(ctx context.Context, session authn.Session, user mgclients.Client) (mgclients.Client, error) {
user, err := es.svc.UpdateClientRole(ctx, session, user)
func (es *eventStore) UpdateRole(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
user, err := es.svc.UpdateRole(ctx, session, user)
if err != nil {
return user, err
}
@@ -72,8 +71,8 @@ func (es *eventStore) UpdateClientRole(ctx context.Context, session authn.Sessio
return es.update(ctx, "role", user)
}
func (es *eventStore) UpdateClientTags(ctx context.Context, session authn.Session, user mgclients.Client) (mgclients.Client, error) {
user, err := es.svc.UpdateClientTags(ctx, session, user)
func (es *eventStore) UpdateTags(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
user, err := es.svc.UpdateTags(ctx, session, user)
if err != nil {
return user, err
}
@@ -81,8 +80,8 @@ func (es *eventStore) UpdateClientTags(ctx context.Context, session authn.Sessio
return es.update(ctx, "tags", user)
}
func (es *eventStore) UpdateClientSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (mgclients.Client, error) {
user, err := es.svc.UpdateClientSecret(ctx, session, oldSecret, newSecret)
func (es *eventStore) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (users.User, error) {
user, err := es.svc.UpdateSecret(ctx, session, oldSecret, newSecret)
if err != nil {
return user, err
}
@@ -90,17 +89,51 @@ func (es *eventStore) UpdateClientSecret(ctx context.Context, session authn.Sess
return es.update(ctx, "secret", user)
}
func (es *eventStore) UpdateClientIdentity(ctx context.Context, session authn.Session, id, identity string) (mgclients.Client, error) {
user, err := es.svc.UpdateClientIdentity(ctx, session, id, identity)
func (es *eventStore) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (users.User, error) {
user, err := es.svc.UpdateUsername(ctx, session, id, username)
if err != nil {
return user, err
}
return es.update(ctx, "identity", user)
event := updateUsernameEvent{
user,
}
if err := es.Publish(ctx, event); err != nil {
return user, err
}
return user, nil
}
func (es *eventStore) update(ctx context.Context, operation string, user mgclients.Client) (mgclients.Client, error) {
event := updateClientEvent{
func (es *eventStore) UpdateProfilePicture(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
user, err := es.svc.Update(ctx, session, user)
if err != nil {
return user, err
}
event := updateProfilePictureEvent{
user,
}
if err := es.Publish(ctx, event); err != nil {
return user, err
}
return es.update(ctx, "profile_picture", user)
}
func (es *eventStore) UpdateEmail(ctx context.Context, session authn.Session, id, email string) (users.User, error) {
user, err := es.svc.UpdateEmail(ctx, session, id, email)
if err != nil {
return user, err
}
return es.update(ctx, "email", user)
}
func (es *eventStore) update(ctx context.Context, operation string, user users.User) (users.User, error) {
event := updateUserEvent{
user, operation,
}
@@ -111,13 +144,13 @@ func (es *eventStore) update(ctx context.Context, operation string, user mgclien
return user, nil
}
func (es *eventStore) ViewClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
user, err := es.svc.ViewClient(ctx, session, id)
func (es *eventStore) View(ctx context.Context, session authn.Session, id string) (users.User, error) {
user, err := es.svc.View(ctx, session, id)
if err != nil {
return user, err
}
event := viewClientEvent{
event := viewUserEvent{
user,
}
@@ -128,7 +161,7 @@ func (es *eventStore) ViewClient(ctx context.Context, session authn.Session, id
return user, nil
}
func (es *eventStore) ViewProfile(ctx context.Context, session authn.Session) (mgclients.Client, error) {
func (es *eventStore) ViewProfile(ctx context.Context, session authn.Session) (users.User, error) {
user, err := es.svc.ViewProfile(ctx, session)
if err != nil {
return user, err
@@ -145,12 +178,12 @@ func (es *eventStore) ViewProfile(ctx context.Context, session authn.Session) (m
return user, nil
}
func (es *eventStore) ListClients(ctx context.Context, session authn.Session, pm mgclients.Page) (mgclients.ClientsPage, error) {
cp, err := es.svc.ListClients(ctx, session, pm)
func (es *eventStore) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (users.UsersPage, error) {
cp, err := es.svc.ListUsers(ctx, session, pm)
if err != nil {
return cp, err
}
event := listClientEvent{
event := listUserEvent{
pm,
}
@@ -161,12 +194,12 @@ func (es *eventStore) ListClients(ctx context.Context, session authn.Session, pm
return cp, nil
}
func (es *eventStore) SearchUsers(ctx context.Context, pm mgclients.Page) (mgclients.ClientsPage, error) {
func (es *eventStore) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) {
cp, err := es.svc.SearchUsers(ctx, pm)
if err != nil {
return cp, err
}
event := searchClientEvent{
event := searchUserEvent{
pm,
}
@@ -177,12 +210,12 @@ func (es *eventStore) SearchUsers(ctx context.Context, pm mgclients.Page) (mgcli
return cp, nil
}
func (es *eventStore) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm mgclients.Page) (mgclients.MembersPage, error) {
func (es *eventStore) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (users.MembersPage, error) {
mp, err := es.svc.ListMembers(ctx, session, objectKind, objectID, pm)
if err != nil {
return mp, err
}
event := listClientByGroupEvent{
event := listUserByGroupEvent{
pm, objectKind, objectID,
}
@@ -193,8 +226,8 @@ func (es *eventStore) ListMembers(ctx context.Context, session authn.Session, ob
return mp, nil
}
func (es *eventStore) EnableClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
user, err := es.svc.EnableClient(ctx, session, id)
func (es *eventStore) Enable(ctx context.Context, session authn.Session, id string) (users.User, error) {
user, err := es.svc.Enable(ctx, session, id)
if err != nil {
return user, err
}
@@ -202,8 +235,8 @@ func (es *eventStore) EnableClient(ctx context.Context, session authn.Session, i
return es.delete(ctx, user)
}
func (es *eventStore) DisableClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
user, err := es.svc.DisableClient(ctx, session, id)
func (es *eventStore) Disable(ctx context.Context, session authn.Session, id string) (users.User, error) {
user, err := es.svc.Disable(ctx, session, id)
if err != nil {
return user, err
}
@@ -211,8 +244,8 @@ func (es *eventStore) DisableClient(ctx context.Context, session authn.Session,
return es.delete(ctx, user)
}
func (es *eventStore) delete(ctx context.Context, user mgclients.Client) (mgclients.Client, error) {
event := removeClientEvent{
func (es *eventStore) delete(ctx context.Context, user users.User) (users.User, error) {
event := removeUserEvent{
id: user.ID,
updatedAt: user.UpdatedAt,
updatedBy: user.UpdatedBy,
@@ -232,7 +265,7 @@ func (es *eventStore) Identify(ctx context.Context, session authn.Session) (stri
return userID, err
}
event := identifyClientEvent{
event := identifyUserEvent{
userID: userID,
}
@@ -257,14 +290,14 @@ func (es *eventStore) GenerateResetToken(ctx context.Context, email, host string
return es.Publish(ctx, event)
}
func (es *eventStore) IssueToken(ctx context.Context, identity, secret string) (*magistrala.Token, error) {
token, err := es.svc.IssueToken(ctx, identity, secret)
func (es *eventStore) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) {
token, err := es.svc.IssueToken(ctx, username, secret)
if err != nil {
return token, err
}
event := issueTokenEvent{
identity: identity,
username: username,
}
if err := es.Publish(ctx, event); err != nil {
@@ -313,14 +346,14 @@ func (es *eventStore) SendPasswordReset(ctx context.Context, host, email, user,
return es.Publish(ctx, event)
}
func (es *eventStore) OAuthCallback(ctx context.Context, client mgclients.Client) (mgclients.Client, error) {
token, err := es.svc.OAuthCallback(ctx, client)
func (es *eventStore) OAuthCallback(ctx context.Context, user users.User) (users.User, error) {
token, err := es.svc.OAuthCallback(ctx, user)
if err != nil {
return token, err
}
event := oauthCallbackEvent{
clientID: client.ID,
userID: user.ID,
}
if err := es.Publish(ctx, event); err != nil {
@@ -330,26 +363,26 @@ func (es *eventStore) OAuthCallback(ctx context.Context, client mgclients.Client
return token, nil
}
func (es *eventStore) DeleteClient(ctx context.Context, session authn.Session, id string) error {
if err := es.svc.DeleteClient(ctx, session, id); err != nil {
func (es *eventStore) Delete(ctx context.Context, session authn.Session, id string) error {
if err := es.svc.Delete(ctx, session, id); err != nil {
return err
}
event := deleteClientEvent{
event := deleteUserEvent{
id: id,
}
return es.Publish(ctx, event)
}
func (es *eventStore) OAuthAddClientPolicy(ctx context.Context, client mgclients.Client) error {
if err := es.svc.OAuthAddClientPolicy(ctx, client); err != nil {
func (es *eventStore) OAuthAddUserPolicy(ctx context.Context, user users.User) error {
if err := es.svc.OAuthAddUserPolicy(ctx, user); err != nil {
return err
}
event := addClientPolicyEvent{
id: client.ID,
role: client.Role.String(),
event := addUserPolicyEvent{
id: user.ID,
role: user.Role.String(),
}
return es.Publish(ctx, event)
+51 -40
View File
@@ -11,7 +11,6 @@ import (
"github.com/absmach/magistrala/pkg/authn"
"github.com/absmach/magistrala/pkg/authz"
mgauthz "github.com/absmach/magistrala/pkg/authz"
"github.com/absmach/magistrala/pkg/clients"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
"github.com/absmach/magistrala/pkg/policies"
"github.com/absmach/magistrala/users"
@@ -34,94 +33,106 @@ func AuthorizationMiddleware(svc users.Service, authz mgauthz.Authorization, sel
}
}
func (am *authorizationMiddleware) RegisterClient(ctx context.Context, session authn.Session, client clients.Client, selfRegister bool) (clients.Client, error) {
func (am *authorizationMiddleware) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (users.User, error) {
if selfRegister {
if err := am.checkSuperAdmin(ctx, session.UserID); err == nil {
session.SuperAdmin = true
}
}
return am.svc.RegisterClient(ctx, session, client, selfRegister)
return am.svc.Register(ctx, session, user, selfRegister)
}
func (am *authorizationMiddleware) ViewClient(ctx context.Context, session authn.Session, id string) (clients.Client, error) {
func (am *authorizationMiddleware) View(ctx context.Context, session authn.Session, id string) (users.User, error) {
if err := am.checkSuperAdmin(ctx, session.UserID); err == nil {
session.SuperAdmin = true
}
return am.svc.ViewClient(ctx, session, id)
return am.svc.View(ctx, session, id)
}
func (am *authorizationMiddleware) ViewProfile(ctx context.Context, session authn.Session) (clients.Client, error) {
func (am *authorizationMiddleware) ViewProfile(ctx context.Context, session authn.Session) (users.User, error) {
return am.svc.ViewProfile(ctx, session)
}
func (am *authorizationMiddleware) ListClients(ctx context.Context, session authn.Session, pm clients.Page) (clients.ClientsPage, error) {
func (am *authorizationMiddleware) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (users.UsersPage, error) {
if err := am.checkSuperAdmin(ctx, session.UserID); err == nil {
session.SuperAdmin = true
}
return am.svc.ListClients(ctx, session, pm)
return am.svc.ListUsers(ctx, session, pm)
}
func (am *authorizationMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm clients.Page) (clients.MembersPage, error) {
func (am *authorizationMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (users.MembersPage, error) {
if session.DomainUserID == "" {
return clients.MembersPage{}, svcerr.ErrDomainAuthorization
return users.MembersPage{}, svcerr.ErrDomainAuthorization
}
switch objectKind {
case policies.GroupsKind:
if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.UserID, mgauth.SwitchToPermission(pm.Permission), policies.GroupType, objectID); err != nil {
return clients.MembersPage{}, err
return users.MembersPage{}, err
}
case policies.DomainsKind:
if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.UserID, mgauth.SwitchToPermission(pm.Permission), policies.DomainType, objectID); err != nil {
return clients.MembersPage{}, err
return users.MembersPage{}, err
}
case policies.ThingsKind:
if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.UserID, mgauth.SwitchToPermission(pm.Permission), policies.ThingType, objectID); err != nil {
return clients.MembersPage{}, err
return users.MembersPage{}, err
}
default:
return clients.MembersPage{}, svcerr.ErrAuthorization
return users.MembersPage{}, svcerr.ErrAuthorization
}
return am.svc.ListMembers(ctx, session, objectKind, objectID, pm)
}
func (am *authorizationMiddleware) SearchUsers(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
func (am *authorizationMiddleware) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) {
return am.svc.SearchUsers(ctx, pm)
}
func (am *authorizationMiddleware) UpdateClient(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) {
func (am *authorizationMiddleware) Update(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
if err := am.checkSuperAdmin(ctx, session.UserID); err == nil {
session.SuperAdmin = true
}
return am.svc.UpdateClient(ctx, session, client)
return am.svc.Update(ctx, session, user)
}
func (am *authorizationMiddleware) UpdateClientTags(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) {
func (am *authorizationMiddleware) UpdateTags(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
if err := am.checkSuperAdmin(ctx, session.UserID); err == nil {
session.SuperAdmin = true
}
return am.svc.UpdateClientTags(ctx, session, client)
return am.svc.UpdateTags(ctx, session, user)
}
func (am *authorizationMiddleware) UpdateClientIdentity(ctx context.Context, session authn.Session, id, identity string) (clients.Client, error) {
func (am *authorizationMiddleware) UpdateEmail(ctx context.Context, session authn.Session, id, email string) (users.User, error) {
if err := am.checkSuperAdmin(ctx, session.UserID); err == nil {
session.SuperAdmin = true
}
return am.svc.UpdateClientIdentity(ctx, session, id, identity)
return am.svc.UpdateEmail(ctx, session, id, email)
}
func (am *authorizationMiddleware) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (users.User, error) {
if err := am.checkSuperAdmin(ctx, session.UserID); err == nil {
session.SuperAdmin = true
}
return am.svc.UpdateUsername(ctx, session, id, username)
}
func (am *authorizationMiddleware) UpdateProfilePicture(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
return am.svc.UpdateProfilePicture(ctx, session, user)
}
func (am *authorizationMiddleware) GenerateResetToken(ctx context.Context, email, host string) error {
return am.svc.GenerateResetToken(ctx, email, host)
}
func (am *authorizationMiddleware) UpdateClientSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (clients.Client, error) {
return am.svc.UpdateClientSecret(ctx, session, oldSecret, newSecret)
func (am *authorizationMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (users.User, error) {
return am.svc.UpdateSecret(ctx, session, oldSecret, newSecret)
}
func (am *authorizationMiddleware) ResetSecret(ctx context.Context, session authn.Session, secret string) error {
@@ -132,62 +143,62 @@ func (am *authorizationMiddleware) SendPasswordReset(ctx context.Context, host,
return am.svc.SendPasswordReset(ctx, host, email, user, token)
}
func (am *authorizationMiddleware) UpdateClientRole(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) {
func (am *authorizationMiddleware) UpdateRole(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
if err := am.checkSuperAdmin(ctx, session.UserID); err == nil {
session.SuperAdmin = true
}
if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, client.ID, policies.MembershipPermission, policies.PlatformType, policies.MagistralaObject); err != nil {
return clients.Client{}, err
if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, user.ID, policies.MembershipPermission, policies.PlatformType, policies.MagistralaObject); err != nil {
return users.User{}, err
}
return am.svc.UpdateClientRole(ctx, session, client)
return am.svc.UpdateRole(ctx, session, user)
}
func (am *authorizationMiddleware) EnableClient(ctx context.Context, session authn.Session, id string) (clients.Client, error) {
func (am *authorizationMiddleware) Enable(ctx context.Context, session authn.Session, id string) (users.User, error) {
if err := am.checkSuperAdmin(ctx, session.UserID); err == nil {
session.SuperAdmin = true
}
return am.svc.EnableClient(ctx, session, id)
return am.svc.Enable(ctx, session, id)
}
func (am *authorizationMiddleware) DisableClient(ctx context.Context, session authn.Session, id string) (clients.Client, error) {
func (am *authorizationMiddleware) Disable(ctx context.Context, session authn.Session, id string) (users.User, error) {
if err := am.checkSuperAdmin(ctx, session.UserID); err == nil {
session.SuperAdmin = true
}
return am.svc.DisableClient(ctx, session, id)
return am.svc.Disable(ctx, session, id)
}
func (am *authorizationMiddleware) DeleteClient(ctx context.Context, session authn.Session, id string) error {
func (am *authorizationMiddleware) Delete(ctx context.Context, session authn.Session, id string) error {
if err := am.checkSuperAdmin(ctx, session.UserID); err == nil {
session.SuperAdmin = true
}
return am.svc.DeleteClient(ctx, session, id)
return am.svc.Delete(ctx, session, id)
}
func (am *authorizationMiddleware) Identify(ctx context.Context, session authn.Session) (string, error) {
return am.svc.Identify(ctx, session)
}
func (am *authorizationMiddleware) IssueToken(ctx context.Context, identity, secret string) (*magistrala.Token, error) {
return am.svc.IssueToken(ctx, identity, secret)
func (am *authorizationMiddleware) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) {
return am.svc.IssueToken(ctx, username, secret)
}
func (am *authorizationMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error) {
return am.svc.RefreshToken(ctx, session, refreshToken)
}
func (am *authorizationMiddleware) OAuthCallback(ctx context.Context, client clients.Client) (clients.Client, error) {
return am.svc.OAuthCallback(ctx, client)
func (am *authorizationMiddleware) OAuthCallback(ctx context.Context, user users.User) (users.User, error) {
return am.svc.OAuthCallback(ctx, user)
}
func (am *authorizationMiddleware) OAuthAddClientPolicy(ctx context.Context, client clients.Client) error {
if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, client.ID, policies.MembershipPermission, policies.PlatformType, policies.MagistralaObject); err == nil {
func (am *authorizationMiddleware) OAuthAddUserPolicy(ctx context.Context, user users.User) error {
if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, user.ID, policies.MembershipPermission, policies.PlatformType, policies.MagistralaObject); err == nil {
return nil
}
return am.svc.OAuthAddClientPolicy(ctx, client)
return am.svc.OAuthAddUserPolicy(ctx, user)
}
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, adminID string) error {
+108 -70
View File
@@ -10,7 +10,6 @@ import (
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/pkg/authn"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/users"
)
@@ -21,20 +20,21 @@ type loggingMiddleware struct {
svc users.Service
}
// LoggingMiddleware adds logging facilities to the clients service.
// LoggingMiddleware adds logging facilities to the users service.
func LoggingMiddleware(svc users.Service, logger *slog.Logger) users.Service {
return &loggingMiddleware{logger, svc}
}
// RegisterClient logs the register_client request. It logs the client id and the time it took to complete the request.
// Register logs the user request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) RegisterClient(ctx context.Context, session authn.Session, client mgclients.Client, selfRegister bool) (c mgclients.Client, err error) {
func (lm *loggingMiddleware) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (u users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", c.ID),
slog.String("name", c.Name),
slog.String("username", user.Credentials.Username),
slog.String("first_name", user.FirstName),
slog.String("last_name", user.LastName),
),
}
if err != nil {
@@ -42,14 +42,15 @@ func (lm *loggingMiddleware) RegisterClient(ctx context.Context, session authn.S
lm.logger.Warn("Register user failed", args...)
return
}
args = append(args, slog.String("user_id", u.ID))
lm.logger.Info("Register user completed successfully", args...)
}(time.Now())
return lm.svc.RegisterClient(ctx, session, client, selfRegister)
return lm.svc.Register(ctx, session, user, selfRegister)
}
// IssueToken logs the issue_token request. It logs the client identity type and the time it took to complete the request.
// IssueToken logs the issue_token request. It logs the username type and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) IssueToken(ctx context.Context, identity, secret string) (t *magistrala.Token, err error) {
func (lm *loggingMiddleware) IssueToken(ctx context.Context, username, secret string) (t *magistrala.Token, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
@@ -64,7 +65,7 @@ func (lm *loggingMiddleware) IssueToken(ctx context.Context, identity, secret st
}
lm.logger.Info("Issue token completed successfully", args...)
}(time.Now())
return lm.svc.IssueToken(ctx, identity, secret)
return lm.svc.IssueToken(ctx, username, secret)
}
// RefreshToken logs the refresh_token request. It logs the refreshtoken, token type and the time it took to complete the request.
@@ -87,15 +88,14 @@ func (lm *loggingMiddleware) RefreshToken(ctx context.Context, session authn.Ses
return lm.svc.RefreshToken(ctx, session, refreshToken)
}
// ViewClient logs the view_client request. It logs the client id and the time it took to complete the request.
// View logs the view_user request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) ViewClient(ctx context.Context, session authn.Session, id string) (c mgclients.Client, err error) {
func (lm *loggingMiddleware) View(ctx context.Context, session authn.Session, id string) (c users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", id),
slog.String("name", c.Name),
),
}
if err != nil {
@@ -105,18 +105,18 @@ func (lm *loggingMiddleware) ViewClient(ctx context.Context, session authn.Sessi
}
lm.logger.Info("View user completed successfully", args...)
}(time.Now())
return lm.svc.ViewClient(ctx, session, id)
return lm.svc.View(ctx, session, id)
}
// ViewProfile logs the view_profile request. It logs the client id and the time it took to complete the request.
// ViewProfile logs the view_profile request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) ViewProfile(ctx context.Context, session authn.Session) (c mgclients.Client, err error) {
func (lm *loggingMiddleware) ViewProfile(ctx context.Context, session authn.Session) (c users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", c.ID),
slog.String("name", c.Name),
slog.String("username", c.Credentials.Username),
),
}
if err != nil {
@@ -129,9 +129,9 @@ func (lm *loggingMiddleware) ViewProfile(ctx context.Context, session authn.Sess
return lm.svc.ViewProfile(ctx, session)
}
// ListClients logs the list_clients request. It logs the page metadata and the time it took to complete the request.
// ListUsers logs the list_users request. It logs the page metadata and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) ListClients(ctx context.Context, session authn.Session, pm mgclients.Page) (cp mgclients.ClientsPage, err error) {
func (lm *loggingMiddleware) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (cp users.UsersPage, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
@@ -148,11 +148,11 @@ func (lm *loggingMiddleware) ListClients(ctx context.Context, session authn.Sess
}
lm.logger.Info("List users completed successfully", args...)
}(time.Now())
return lm.svc.ListClients(ctx, session, pm)
return lm.svc.ListUsers(ctx, session, pm)
}
// SearchUsers logs the search_users request. It logs the page metadata and the time it took to complete the request.
func (lm *loggingMiddleware) SearchUsers(ctx context.Context, cp mgclients.Page) (mp mgclients.ClientsPage, err error) {
func (lm *loggingMiddleware) SearchUsers(ctx context.Context, cp users.Page) (mp users.UsersPage, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
@@ -164,24 +164,26 @@ func (lm *loggingMiddleware) SearchUsers(ctx context.Context, cp mgclients.Page)
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Search clients failed to complete successfully", args...)
lm.logger.Warn("Search users failed to complete successfully", args...)
return
}
lm.logger.Info("Search clients completed successfully", args...)
lm.logger.Info("Search users completed successfully", args...)
}(time.Now())
return lm.svc.SearchUsers(ctx, cp)
}
// UpdateClient logs the update_client request. It logs the client id and the time it took to complete the request.
// Update logs the update_user request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) UpdateClient(ctx context.Context, session authn.Session, client mgclients.Client) (c mgclients.Client, err error) {
func (lm *loggingMiddleware) Update(ctx context.Context, session authn.Session, user users.User) (u users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", c.ID),
slog.String("name", c.Name),
slog.Any("metadata", c.Metadata),
slog.String("id", u.ID),
slog.String("username", u.Credentials.Username),
slog.String("first_name", u.FirstName),
slog.String("last_name", u.LastName),
slog.Any("metadata", u.Metadata),
),
}
if err != nil {
@@ -191,18 +193,17 @@ func (lm *loggingMiddleware) UpdateClient(ctx context.Context, session authn.Ses
}
lm.logger.Info("Update user completed successfully", args...)
}(time.Now())
return lm.svc.UpdateClient(ctx, session, client)
return lm.svc.Update(ctx, session, user)
}
// UpdateClientTags logs the update_client_tags request. It logs the client id and the time it took to complete the request.
// UpdateTags logs the update_user_tags request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) UpdateClientTags(ctx context.Context, session authn.Session, client mgclients.Client) (c mgclients.Client, err error) {
func (lm *loggingMiddleware) UpdateTags(ctx context.Context, session authn.Session, user users.User) (c users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", c.ID),
slog.String("name", c.Name),
slog.Any("tags", c.Tags),
),
}
@@ -213,39 +214,38 @@ func (lm *loggingMiddleware) UpdateClientTags(ctx context.Context, session authn
}
lm.logger.Info("Update user tags completed successfully", args...)
}(time.Now())
return lm.svc.UpdateClientTags(ctx, session, client)
return lm.svc.UpdateTags(ctx, session, user)
}
// UpdateClientIdentity logs the update_identity request. It logs the client id and the time it took to complete the request.
// UpdateEmail logs the update_user_email request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) UpdateClientIdentity(ctx context.Context, session authn.Session, id, identity string) (c mgclients.Client, err error) {
func (lm *loggingMiddleware) UpdateEmail(ctx context.Context, session authn.Session, id, email string) (c users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", c.ID),
slog.String("name", c.Name),
slog.String("email", c.Email),
),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Update client identity failed", args...)
lm.logger.Warn("Update user email failed", args...)
return
}
lm.logger.Info("Update client identity completed successfully", args...)
lm.logger.Info("Update user email completed successfully", args...)
}(time.Now())
return lm.svc.UpdateClientIdentity(ctx, session, id, identity)
return lm.svc.UpdateEmail(ctx, session, id, email)
}
// UpdateClientSecret logs the update_client_secret request. It logs the client id and the time it took to complete the request.
// UpdateSecret logs the update_user_secret request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) UpdateClientSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (c mgclients.Client, err error) {
func (lm *loggingMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (c users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", c.ID),
slog.String("name", c.Name),
),
}
if err != nil {
@@ -255,7 +255,48 @@ func (lm *loggingMiddleware) UpdateClientSecret(ctx context.Context, session aut
}
lm.logger.Info("Update user secret completed successfully", args...)
}(time.Now())
return lm.svc.UpdateClientSecret(ctx, session, oldSecret, newSecret)
return lm.svc.UpdateSecret(ctx, session, oldSecret, newSecret)
}
// UpdateUsername logs the update_usernames request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (u users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", u.ID),
slog.String("username", u.Credentials.Username),
),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Update user names failed", args...)
return
}
lm.logger.Info("Update user names completed successfully", args...)
}(time.Now())
return lm.svc.UpdateUsername(ctx, session, id, username)
}
// UpdateProfilePicture logs the update_profile_picture request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) UpdateProfilePicture(ctx context.Context, session authn.Session, user users.User) (u users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", u.ID),
),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Update profile picture failed", args...)
return
}
lm.logger.Info("Update profile picture completed successfully", args...)
}(time.Now())
return lm.svc.Update(ctx, session, user)
}
// GenerateResetToken logs the generate_reset_token request. It logs the time it took to complete the request.
@@ -311,16 +352,15 @@ func (lm *loggingMiddleware) SendPasswordReset(ctx context.Context, host, email,
return lm.svc.SendPasswordReset(ctx, host, email, user, token)
}
// UpdateClientRole logs the update_client_role request. It logs the client id and the time it took to complete the request.
// UpdateRole logs the update_user_role request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) UpdateClientRole(ctx context.Context, session authn.Session, client mgclients.Client) (c mgclients.Client, err error) {
func (lm *loggingMiddleware) UpdateRole(ctx context.Context, session authn.Session, user users.User) (c users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", c.ID),
slog.String("name", c.Name),
slog.String("role", client.Role.String()),
slog.String("id", user.ID),
slog.String("role", user.Role.String()),
),
}
if err != nil {
@@ -330,18 +370,17 @@ func (lm *loggingMiddleware) UpdateClientRole(ctx context.Context, session authn
}
lm.logger.Info("Update user role completed successfully", args...)
}(time.Now())
return lm.svc.UpdateClientRole(ctx, session, client)
return lm.svc.UpdateRole(ctx, session, user)
}
// EnableClient logs the enable_client request. It logs the client id and the time it took to complete the request.
// Enable logs the enable_user request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) EnableClient(ctx context.Context, session authn.Session, id string) (c mgclients.Client, err error) {
func (lm *loggingMiddleware) Enable(ctx context.Context, session authn.Session, id string) (c users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", id),
slog.String("name", c.Name),
),
}
if err != nil {
@@ -351,18 +390,17 @@ func (lm *loggingMiddleware) EnableClient(ctx context.Context, session authn.Ses
}
lm.logger.Info("Enable user completed successfully", args...)
}(time.Now())
return lm.svc.EnableClient(ctx, session, id)
return lm.svc.Enable(ctx, session, id)
}
// DisableClient logs the disable_client request. It logs the client id and the time it took to complete the request.
// Disable logs the disable_user request. It logs the user id and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) DisableClient(ctx context.Context, session authn.Session, id string) (c mgclients.Client, err error) {
func (lm *loggingMiddleware) Disable(ctx context.Context, session authn.Session, id string) (c users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("user",
slog.String("id", id),
slog.String("name", c.Name),
),
}
if err != nil {
@@ -372,12 +410,12 @@ func (lm *loggingMiddleware) DisableClient(ctx context.Context, session authn.Se
}
lm.logger.Info("Disable user completed successfully", args...)
}(time.Now())
return lm.svc.DisableClient(ctx, session, id)
return lm.svc.Disable(ctx, session, id)
}
// ListMembers logs the list_members request. It logs the group id, and the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, cp mgclients.Page) (mp mgclients.MembersPage, err error) {
func (lm *loggingMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, cp users.Page) (mp users.MembersPage, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
@@ -418,11 +456,11 @@ func (lm *loggingMiddleware) Identify(ctx context.Context, session authn.Session
return lm.svc.Identify(ctx, session)
}
func (lm *loggingMiddleware) OAuthCallback(ctx context.Context, client mgclients.Client) (c mgclients.Client, err error) {
func (lm *loggingMiddleware) OAuthCallback(ctx context.Context, user users.User) (c users.User, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("user_id", client.ID),
slog.String("user_id", user.ID),
}
if err != nil {
args = append(args, slog.Any("error", err))
@@ -431,11 +469,11 @@ func (lm *loggingMiddleware) OAuthCallback(ctx context.Context, client mgclients
}
lm.logger.Info("OAuth callback completed successfully", args...)
}(time.Now())
return lm.svc.OAuthCallback(ctx, client)
return lm.svc.OAuthCallback(ctx, user)
}
// DeleteClient logs the delete_client request. It logs the client id and token and the time it took to complete the request.
func (lm *loggingMiddleware) DeleteClient(ctx context.Context, session authn.Session, id string) (err error) {
// Delete logs the delete_user request. It logs the user id and token and the time it took to complete the request.
func (lm *loggingMiddleware) Delete(ctx context.Context, session authn.Session, id string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
@@ -448,22 +486,22 @@ func (lm *loggingMiddleware) DeleteClient(ctx context.Context, session authn.Ses
}
lm.logger.Info("Delete user completed successfully", args...)
}(time.Now())
return lm.svc.DeleteClient(ctx, session, id)
return lm.svc.Delete(ctx, session, id)
}
// OAuthAddClientPolicy logs the add_client_policy request. It logs the client id and the time it took to complete the request.
func (lm *loggingMiddleware) OAuthAddClientPolicy(ctx context.Context, client mgclients.Client) (err error) {
// OAuthAddUserPolicy logs the add_user_policy request. It logs the user id and the time it took to complete the request.
func (lm *loggingMiddleware) OAuthAddUserPolicy(ctx context.Context, user users.User) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("user_id", client.ID),
slog.String("user_id", user.ID),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Add client policy failed", args...)
lm.logger.Warn("Add user policy failed", args...)
return
}
lm.logger.Info("Add client policy completed successfully", args...)
lm.logger.Info("Add user policy completed successfully", args...)
}(time.Now())
return lm.svc.OAuthAddClientPolicy(ctx, client)
return lm.svc.OAuthAddUserPolicy(ctx, user)
}
+86 -69
View File
@@ -9,7 +9,6 @@ import (
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/pkg/authn"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/users"
"github.com/go-kit/kit/metrics"
)
@@ -31,22 +30,22 @@ func MetricsMiddleware(svc users.Service, counter metrics.Counter, latency metri
}
}
// RegisterClient instruments RegisterClient method with metrics.
func (ms *metricsMiddleware) RegisterClient(ctx context.Context, session authn.Session, client mgclients.Client, selfRegister bool) (mgclients.Client, error) {
// Register instruments Register method with metrics.
func (ms *metricsMiddleware) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "register_client").Add(1)
ms.latency.With("method", "register_client").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "register_user").Add(1)
ms.latency.With("method", "register_user").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.RegisterClient(ctx, session, client, selfRegister)
return ms.svc.Register(ctx, session, user, selfRegister)
}
// IssueToken instruments IssueToken method with metrics.
func (ms *metricsMiddleware) IssueToken(ctx context.Context, identity, secret string) (*magistrala.Token, error) {
func (ms *metricsMiddleware) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) {
defer func(begin time.Time) {
ms.counter.With("method", "issue_token").Add(1)
ms.latency.With("method", "issue_token").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.IssueToken(ctx, identity, secret)
return ms.svc.IssueToken(ctx, username, secret)
}
// RefreshToken instruments RefreshToken method with metrics.
@@ -58,17 +57,17 @@ func (ms *metricsMiddleware) RefreshToken(ctx context.Context, session authn.Ses
return ms.svc.RefreshToken(ctx, session, refreshToken)
}
// ViewClient instruments ViewClient method with metrics.
func (ms *metricsMiddleware) ViewClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
// View instruments View method with metrics.
func (ms *metricsMiddleware) View(ctx context.Context, session authn.Session, id string) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "view_client").Add(1)
ms.latency.With("method", "view_client").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "view_user").Add(1)
ms.latency.With("method", "view_user").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ViewClient(ctx, session, id)
return ms.svc.View(ctx, session, id)
}
// ViewProfile instruments ViewProfile method with metrics.
func (ms *metricsMiddleware) ViewProfile(ctx context.Context, session authn.Session) (mgclients.Client, error) {
func (ms *metricsMiddleware) ViewProfile(ctx context.Context, session authn.Session) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "view_profile").Add(1)
ms.latency.With("method", "view_profile").Observe(time.Since(begin).Seconds())
@@ -76,17 +75,17 @@ func (ms *metricsMiddleware) ViewProfile(ctx context.Context, session authn.Sess
return ms.svc.ViewProfile(ctx, session)
}
// ListClients instruments ListClients method with metrics.
func (ms *metricsMiddleware) ListClients(ctx context.Context, session authn.Session, pm mgclients.Page) (mgclients.ClientsPage, error) {
// ListUsers instruments ListUsers method with metrics.
func (ms *metricsMiddleware) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (users.UsersPage, error) {
defer func(begin time.Time) {
ms.counter.With("method", "list_clients").Add(1)
ms.latency.With("method", "list_clients").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "list_users").Add(1)
ms.latency.With("method", "list_users").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ListClients(ctx, session, pm)
return ms.svc.ListUsers(ctx, session, pm)
}
// SearchUsers instruments SearchClients method with metrics.
func (ms *metricsMiddleware) SearchUsers(ctx context.Context, pm mgclients.Page) (mp mgclients.ClientsPage, err error) {
// SearchUsers instruments SearchUsers method with metrics.
func (ms *metricsMiddleware) SearchUsers(ctx context.Context, pm users.Page) (mp users.UsersPage, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "search_users").Add(1)
ms.latency.With("method", "search_users").Observe(time.Since(begin).Seconds())
@@ -94,40 +93,58 @@ func (ms *metricsMiddleware) SearchUsers(ctx context.Context, pm mgclients.Page)
return ms.svc.SearchUsers(ctx, pm)
}
// UpdateClient instruments UpdateClient method with metrics.
func (ms *metricsMiddleware) UpdateClient(ctx context.Context, session authn.Session, client mgclients.Client) (mgclients.Client, error) {
// Update instruments Update method with metrics.
func (ms *metricsMiddleware) Update(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "update_client_name_and_metadata").Add(1)
ms.latency.With("method", "update_client_name_and_metadata").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "update_user").Add(1)
ms.latency.With("method", "update_user").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.UpdateClient(ctx, session, client)
return ms.svc.Update(ctx, session, user)
}
// UpdateClientTags instruments UpdateClientTags method with metrics.
func (ms *metricsMiddleware) UpdateClientTags(ctx context.Context, session authn.Session, client mgclients.Client) (mgclients.Client, error) {
// UpdateTags instruments UpdateTags method with metrics.
func (ms *metricsMiddleware) UpdateTags(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "update_client_tags").Add(1)
ms.latency.With("method", "update_client_tags").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "update_user_tags").Add(1)
ms.latency.With("method", "update_user_tags").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.UpdateClientTags(ctx, session, client)
return ms.svc.UpdateTags(ctx, session, user)
}
// UpdateClientIdentity instruments UpdateClientIdentity method with metrics.
func (ms *metricsMiddleware) UpdateClientIdentity(ctx context.Context, session authn.Session, id, identity string) (mgclients.Client, error) {
// UpdateEmail instruments UpdateEmail method with metrics.
func (ms *metricsMiddleware) UpdateEmail(ctx context.Context, session authn.Session, id, email string) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "update_client_identity").Add(1)
ms.latency.With("method", "update_client_identity").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "update_user_email").Add(1)
ms.latency.With("method", "update_user_email").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.UpdateClientIdentity(ctx, session, id, identity)
return ms.svc.UpdateEmail(ctx, session, id, email)
}
// UpdateClientSecret instruments UpdateClientSecret method with metrics.
func (ms *metricsMiddleware) UpdateClientSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (mgclients.Client, error) {
// UpdateSecret instruments UpdateSecret method with metrics.
func (ms *metricsMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "update_client_secret").Add(1)
ms.latency.With("method", "update_client_secret").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "update_user_secret").Add(1)
ms.latency.With("method", "update_user_secret").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.UpdateClientSecret(ctx, session, oldSecret, newSecret)
return ms.svc.UpdateSecret(ctx, session, oldSecret, newSecret)
}
// UpdateUsername instruments UpdateUsername method with metrics.
func (ms *metricsMiddleware) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "update_usernames").Add(1)
ms.latency.With("method", "update_usernames").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.UpdateUsername(ctx, session, id, username)
}
// UpdateProfilePicture instruments UpdateProfilePicture method with metrics.
func (ms *metricsMiddleware) UpdateProfilePicture(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "update_profile_picture").Add(1)
ms.latency.With("method", "update_profile_picture").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Update(ctx, session, user)
}
// GenerateResetToken instruments GenerateResetToken method with metrics.
@@ -157,35 +174,35 @@ func (ms *metricsMiddleware) SendPasswordReset(ctx context.Context, host, email,
return ms.svc.SendPasswordReset(ctx, host, email, user, token)
}
// UpdateClientRole instruments UpdateClientRole method with metrics.
func (ms *metricsMiddleware) UpdateClientRole(ctx context.Context, session authn.Session, client mgclients.Client) (mgclients.Client, error) {
// UpdateRole instruments UpdateRole method with metrics.
func (ms *metricsMiddleware) UpdateRole(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "update_client_role").Add(1)
ms.latency.With("method", "update_client_role").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "update_user_role").Add(1)
ms.latency.With("method", "update_user_role").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.UpdateClientRole(ctx, session, client)
return ms.svc.UpdateRole(ctx, session, user)
}
// EnableClient instruments EnableClient method with metrics.
func (ms *metricsMiddleware) EnableClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
// Enable instruments Enable method with metrics.
func (ms *metricsMiddleware) Enable(ctx context.Context, session authn.Session, id string) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "enable_client").Add(1)
ms.latency.With("method", "enable_client").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "enable_user").Add(1)
ms.latency.With("method", "enable_user").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.EnableClient(ctx, session, id)
return ms.svc.Enable(ctx, session, id)
}
// DisableClient instruments DisableClient method with metrics.
func (ms *metricsMiddleware) DisableClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
// Disable instruments Disable method with metrics.
func (ms *metricsMiddleware) Disable(ctx context.Context, session authn.Session, id string) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "disable_client").Add(1)
ms.latency.With("method", "disable_client").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "disable_user").Add(1)
ms.latency.With("method", "disable_user").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.DisableClient(ctx, session, id)
return ms.svc.Disable(ctx, session, id)
}
// ListMembers instruments ListMembers method with metrics.
func (ms *metricsMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm mgclients.Page) (mp mgclients.MembersPage, err error) {
func (ms *metricsMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (mp users.MembersPage, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "list_members").Add(1)
ms.latency.With("method", "list_members").Observe(time.Since(begin).Seconds())
@@ -203,28 +220,28 @@ func (ms *metricsMiddleware) Identify(ctx context.Context, session authn.Session
}
// OAuthCallback instruments OAuthCallback method with metrics.
func (ms *metricsMiddleware) OAuthCallback(ctx context.Context, client mgclients.Client) (mgclients.Client, error) {
func (ms *metricsMiddleware) OAuthCallback(ctx context.Context, user users.User) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "oauth_callback").Add(1)
ms.latency.With("method", "oauth_callback").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.OAuthCallback(ctx, client)
return ms.svc.OAuthCallback(ctx, user)
}
// DeleteClient instruments DeleteClient method with metrics.
func (ms *metricsMiddleware) DeleteClient(ctx context.Context, session authn.Session, id string) error {
// Delete instruments Delete method with metrics.
func (ms *metricsMiddleware) Delete(ctx context.Context, session authn.Session, id string) error {
defer func(begin time.Time) {
ms.counter.With("method", "delete_client").Add(1)
ms.latency.With("method", "delete_client").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "delete_user").Add(1)
ms.latency.With("method", "delete_user").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.DeleteClient(ctx, session, id)
return ms.svc.Delete(ctx, session, id)
}
// OAuthAddClientPolicy instruments OAuthAddClientPolicy method with metrics.
func (ms *metricsMiddleware) OAuthAddClientPolicy(ctx context.Context, client mgclients.Client) error {
// OAuthAddUserPolicy instruments OAuthAddUserPolicy method with metrics.
func (ms *metricsMiddleware) OAuthAddUserPolicy(ctx context.Context, user users.User) error {
defer func(begin time.Time) {
ms.counter.With("method", "add_client_policy").Add(1)
ms.latency.With("method", "add_client_policy").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "add_user_policy").Add(1)
ms.latency.With("method", "add_user_policy").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.OAuthAddClientPolicy(ctx, client)
return ms.svc.OAuthAddUserPolicy(ctx, user)
}
+121 -150
View File
@@ -7,8 +7,7 @@ package mocks
import (
context "context"
clients "github.com/absmach/magistrala/pkg/clients"
users "github.com/absmach/magistrala/users"
mock "github.com/stretchr/testify/mock"
)
@@ -17,27 +16,27 @@ type Repository struct {
mock.Mock
}
// ChangeStatus provides a mock function with given fields: ctx, client
func (_m *Repository) ChangeStatus(ctx context.Context, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, client)
// ChangeStatus provides a mock function with given fields: ctx, user
func (_m *Repository) ChangeStatus(ctx context.Context, user users.User) (users.User, error) {
ret := _m.Called(ctx, user)
if len(ret) == 0 {
panic("no return value specified for ChangeStatus")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok {
return rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok {
return rf(ctx, user)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok {
r0 = rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok {
r0 = rf(ctx, user)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok {
r1 = rf(ctx, client)
if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok {
r1 = rf(ctx, user)
} else {
r1 = ret.Error(1)
}
@@ -82,25 +81,25 @@ func (_m *Repository) Delete(ctx context.Context, id string) error {
}
// RetrieveAll provides a mock function with given fields: ctx, pm
func (_m *Repository) RetrieveAll(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
func (_m *Repository) RetrieveAll(ctx context.Context, pm users.Page) (users.UsersPage, error) {
ret := _m.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for RetrieveAll")
}
var r0 clients.ClientsPage
var r0 users.UsersPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) (clients.ClientsPage, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, users.Page) (users.UsersPage, error)); ok {
return rf(ctx, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) clients.ClientsPage); ok {
if rf, ok := ret.Get(0).(func(context.Context, users.Page) users.UsersPage); ok {
r0 = rf(ctx, pm)
} else {
r0 = ret.Get(0).(clients.ClientsPage)
r0 = ret.Get(0).(users.UsersPage)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Page) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, users.Page) error); ok {
r1 = rf(ctx, pm)
} else {
r1 = ret.Error(1)
@@ -110,25 +109,25 @@ func (_m *Repository) RetrieveAll(ctx context.Context, pm clients.Page) (clients
}
// RetrieveAllByIDs provides a mock function with given fields: ctx, pm
func (_m *Repository) RetrieveAllByIDs(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
func (_m *Repository) RetrieveAllByIDs(ctx context.Context, pm users.Page) (users.UsersPage, error) {
ret := _m.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for RetrieveAllByIDs")
}
var r0 clients.ClientsPage
var r0 users.UsersPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) (clients.ClientsPage, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, users.Page) (users.UsersPage, error)); ok {
return rf(ctx, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) clients.ClientsPage); ok {
if rf, ok := ret.Get(0).(func(context.Context, users.Page) users.UsersPage); ok {
r0 = rf(ctx, pm)
} else {
r0 = ret.Get(0).(clients.ClientsPage)
r0 = ret.Get(0).(users.UsersPage)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Page) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, users.Page) error); ok {
r1 = rf(ctx, pm)
} else {
r1 = ret.Error(1)
@@ -137,23 +136,51 @@ func (_m *Repository) RetrieveAllByIDs(ctx context.Context, pm clients.Page) (cl
return r0, r1
}
// RetrieveByEmail provides a mock function with given fields: ctx, email
func (_m *Repository) RetrieveByEmail(ctx context.Context, email string) (users.User, error) {
ret := _m.Called(ctx, email)
if len(ret) == 0 {
panic("no return value specified for RetrieveByEmail")
}
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (users.User, error)); ok {
return rf(ctx, email)
}
if rf, ok := ret.Get(0).(func(context.Context, string) users.User); ok {
r0 = rf(ctx, email)
} else {
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, email)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RetrieveByID provides a mock function with given fields: ctx, id
func (_m *Repository) RetrieveByID(ctx context.Context, id string) (clients.Client, error) {
func (_m *Repository) RetrieveByID(ctx context.Context, id string) (users.User, error) {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for RetrieveByID")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (clients.Client, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, string) (users.User, error)); ok {
return rf(ctx, id)
}
if rf, ok := ret.Get(0).(func(context.Context, string) clients.Client); ok {
if rf, ok := ret.Get(0).(func(context.Context, string) users.User); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
@@ -165,27 +192,27 @@ func (_m *Repository) RetrieveByID(ctx context.Context, id string) (clients.Clie
return r0, r1
}
// RetrieveByIdentity provides a mock function with given fields: ctx, identity
func (_m *Repository) RetrieveByIdentity(ctx context.Context, identity string) (clients.Client, error) {
ret := _m.Called(ctx, identity)
// RetrieveByUsername provides a mock function with given fields: ctx, username
func (_m *Repository) RetrieveByUsername(ctx context.Context, username string) (users.User, error) {
ret := _m.Called(ctx, username)
if len(ret) == 0 {
panic("no return value specified for RetrieveByIdentity")
panic("no return value specified for RetrieveByUsername")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (clients.Client, error)); ok {
return rf(ctx, identity)
if rf, ok := ret.Get(0).(func(context.Context, string) (users.User, error)); ok {
return rf(ctx, username)
}
if rf, ok := ret.Get(0).(func(context.Context, string) clients.Client); ok {
r0 = rf(ctx, identity)
if rf, ok := ret.Get(0).(func(context.Context, string) users.User); ok {
r0 = rf(ctx, username)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, identity)
r1 = rf(ctx, username)
} else {
r1 = ret.Error(1)
}
@@ -193,27 +220,27 @@ func (_m *Repository) RetrieveByIdentity(ctx context.Context, identity string) (
return r0, r1
}
// Save provides a mock function with given fields: ctx, client
func (_m *Repository) Save(ctx context.Context, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, client)
// Save provides a mock function with given fields: ctx, user
func (_m *Repository) Save(ctx context.Context, user users.User) (users.User, error) {
ret := _m.Called(ctx, user)
if len(ret) == 0 {
panic("no return value specified for Save")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok {
return rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok {
return rf(ctx, user)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok {
r0 = rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok {
r0 = rf(ctx, user)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok {
r1 = rf(ctx, client)
if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok {
r1 = rf(ctx, user)
} else {
r1 = ret.Error(1)
}
@@ -221,26 +248,26 @@ func (_m *Repository) Save(ctx context.Context, client clients.Client) (clients.
return r0, r1
}
// SearchClients provides a mock function with given fields: ctx, pm
func (_m *Repository) SearchClients(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
// SearchUsers provides a mock function with given fields: ctx, pm
func (_m *Repository) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) {
ret := _m.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for SearchClients")
panic("no return value specified for SearchUsers")
}
var r0 clients.ClientsPage
var r0 users.UsersPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) (clients.ClientsPage, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, users.Page) (users.UsersPage, error)); ok {
return rf(ctx, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) clients.ClientsPage); ok {
if rf, ok := ret.Get(0).(func(context.Context, users.Page) users.UsersPage); ok {
r0 = rf(ctx, pm)
} else {
r0 = ret.Get(0).(clients.ClientsPage)
r0 = ret.Get(0).(users.UsersPage)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Page) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, users.Page) error); ok {
r1 = rf(ctx, pm)
} else {
r1 = ret.Error(1)
@@ -249,27 +276,27 @@ func (_m *Repository) SearchClients(ctx context.Context, pm clients.Page) (clien
return r0, r1
}
// Update provides a mock function with given fields: ctx, client
func (_m *Repository) Update(ctx context.Context, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, client)
// Update provides a mock function with given fields: ctx, user
func (_m *Repository) Update(ctx context.Context, user users.User) (users.User, error) {
ret := _m.Called(ctx, user)
if len(ret) == 0 {
panic("no return value specified for Update")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok {
return rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok {
return rf(ctx, user)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok {
r0 = rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok {
r0 = rf(ctx, user)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok {
r1 = rf(ctx, client)
if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok {
r1 = rf(ctx, user)
} else {
r1 = ret.Error(1)
}
@@ -277,83 +304,27 @@ func (_m *Repository) Update(ctx context.Context, client clients.Client) (client
return r0, r1
}
// UpdateIdentity provides a mock function with given fields: ctx, client
func (_m *Repository) UpdateIdentity(ctx context.Context, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, client)
if len(ret) == 0 {
panic("no return value specified for UpdateIdentity")
}
var r0 clients.Client
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok {
return rf(ctx, client)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok {
r0 = rf(ctx, client)
} else {
r0 = ret.Get(0).(clients.Client)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok {
r1 = rf(ctx, client)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateRole provides a mock function with given fields: ctx, client
func (_m *Repository) UpdateRole(ctx context.Context, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, client)
if len(ret) == 0 {
panic("no return value specified for UpdateRole")
}
var r0 clients.Client
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok {
return rf(ctx, client)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok {
r0 = rf(ctx, client)
} else {
r0 = ret.Get(0).(clients.Client)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok {
r1 = rf(ctx, client)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateSecret provides a mock function with given fields: ctx, client
func (_m *Repository) UpdateSecret(ctx context.Context, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, client)
// UpdateSecret provides a mock function with given fields: ctx, user
func (_m *Repository) UpdateSecret(ctx context.Context, user users.User) (users.User, error) {
ret := _m.Called(ctx, user)
if len(ret) == 0 {
panic("no return value specified for UpdateSecret")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok {
return rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok {
return rf(ctx, user)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok {
r0 = rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok {
r0 = rf(ctx, user)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok {
r1 = rf(ctx, client)
if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok {
r1 = rf(ctx, user)
} else {
r1 = ret.Error(1)
}
@@ -361,27 +332,27 @@ func (_m *Repository) UpdateSecret(ctx context.Context, client clients.Client) (
return r0, r1
}
// UpdateTags provides a mock function with given fields: ctx, client
func (_m *Repository) UpdateTags(ctx context.Context, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, client)
// UpdateUsername provides a mock function with given fields: ctx, user
func (_m *Repository) UpdateUsername(ctx context.Context, user users.User) (users.User, error) {
ret := _m.Called(ctx, user)
if len(ret) == 0 {
panic("no return value specified for UpdateTags")
panic("no return value specified for UpdateUsername")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok {
return rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok {
return rf(ctx, user)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok {
r0 = rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok {
r0 = rf(ctx, user)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok {
r1 = rf(ctx, client)
if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok {
r1 = rf(ctx, user)
} else {
r1 = ret.Error(1)
}
+212 -155
View File
@@ -5,14 +5,15 @@
package mocks
import (
authn "github.com/absmach/magistrala/pkg/authn"
clients "github.com/absmach/magistrala/pkg/clients"
context "context"
authn "github.com/absmach/magistrala/pkg/authn"
magistrala "github.com/absmach/magistrala"
mock "github.com/stretchr/testify/mock"
users "github.com/absmach/magistrala/users"
)
// Service is an autogenerated mock type for the Service type
@@ -20,12 +21,12 @@ type Service struct {
mock.Mock
}
// DeleteClient provides a mock function with given fields: ctx, session, id
func (_m *Service) DeleteClient(ctx context.Context, session authn.Session, id string) error {
// Delete provides a mock function with given fields: ctx, session, id
func (_m *Service) Delete(ctx context.Context, session authn.Session, id string) error {
ret := _m.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for DeleteClient")
panic("no return value specified for Delete")
}
var r0 error
@@ -38,23 +39,23 @@ func (_m *Service) DeleteClient(ctx context.Context, session authn.Session, id s
return r0
}
// DisableClient provides a mock function with given fields: ctx, session, id
func (_m *Service) DisableClient(ctx context.Context, session authn.Session, id string) (clients.Client, error) {
// Disable provides a mock function with given fields: ctx, session, id
func (_m *Service) Disable(ctx context.Context, session authn.Session, id string) (users.User, error) {
ret := _m.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for DisableClient")
panic("no return value specified for Disable")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (clients.Client, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (users.User, error)); ok {
return rf(ctx, session, id)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) clients.Client); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) users.User); ok {
r0 = rf(ctx, session, id)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
@@ -66,23 +67,23 @@ func (_m *Service) DisableClient(ctx context.Context, session authn.Session, id
return r0, r1
}
// EnableClient provides a mock function with given fields: ctx, session, id
func (_m *Service) EnableClient(ctx context.Context, session authn.Session, id string) (clients.Client, error) {
// Enable provides a mock function with given fields: ctx, session, id
func (_m *Service) Enable(ctx context.Context, session authn.Session, id string) (users.User, error) {
ret := _m.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for EnableClient")
panic("no return value specified for Enable")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (clients.Client, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (users.User, error)); ok {
return rf(ctx, session, id)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) clients.Client); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) users.User); ok {
r0 = rf(ctx, session, id)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
@@ -170,54 +171,26 @@ func (_m *Service) IssueToken(ctx context.Context, identity string, secret strin
return r0, r1
}
// ListClients provides a mock function with given fields: ctx, session, pm
func (_m *Service) ListClients(ctx context.Context, session authn.Session, pm clients.Page) (clients.ClientsPage, error) {
ret := _m.Called(ctx, session, pm)
if len(ret) == 0 {
panic("no return value specified for ListClients")
}
var r0 clients.ClientsPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Page) (clients.ClientsPage, error)); ok {
return rf(ctx, session, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Page) clients.ClientsPage); ok {
r0 = rf(ctx, session, pm)
} else {
r0 = ret.Get(0).(clients.ClientsPage)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, clients.Page) error); ok {
r1 = rf(ctx, session, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListMembers provides a mock function with given fields: ctx, session, objectKind, objectID, pm
func (_m *Service) ListMembers(ctx context.Context, session authn.Session, objectKind string, objectID string, pm clients.Page) (clients.MembersPage, error) {
func (_m *Service) ListMembers(ctx context.Context, session authn.Session, objectKind string, objectID string, pm users.Page) (users.MembersPage, error) {
ret := _m.Called(ctx, session, objectKind, objectID, pm)
if len(ret) == 0 {
panic("no return value specified for ListMembers")
}
var r0 clients.MembersPage
var r0 users.MembersPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, clients.Page) (clients.MembersPage, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, users.Page) (users.MembersPage, error)); ok {
return rf(ctx, session, objectKind, objectID, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, clients.Page) clients.MembersPage); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, users.Page) users.MembersPage); ok {
r0 = rf(ctx, session, objectKind, objectID, pm)
} else {
r0 = ret.Get(0).(clients.MembersPage)
r0 = ret.Get(0).(users.MembersPage)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, clients.Page) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, users.Page) error); ok {
r1 = rf(ctx, session, objectKind, objectID, pm)
} else {
r1 = ret.Error(1)
@@ -226,17 +199,45 @@ func (_m *Service) ListMembers(ctx context.Context, session authn.Session, objec
return r0, r1
}
// OAuthAddClientPolicy provides a mock function with given fields: ctx, client
func (_m *Service) OAuthAddClientPolicy(ctx context.Context, client clients.Client) error {
ret := _m.Called(ctx, client)
// ListUsers provides a mock function with given fields: ctx, session, pm
func (_m *Service) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (users.UsersPage, error) {
ret := _m.Called(ctx, session, pm)
if len(ret) == 0 {
panic("no return value specified for OAuthAddClientPolicy")
panic("no return value specified for ListUsers")
}
var r0 users.UsersPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.Page) (users.UsersPage, error)); ok {
return rf(ctx, session, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.Page) users.UsersPage); ok {
r0 = rf(ctx, session, pm)
} else {
r0 = ret.Get(0).(users.UsersPage)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.Page) error); ok {
r1 = rf(ctx, session, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// OAuthAddUserPolicy provides a mock function with given fields: ctx, user
func (_m *Service) OAuthAddUserPolicy(ctx context.Context, user users.User) error {
ret := _m.Called(ctx, user)
if len(ret) == 0 {
panic("no return value specified for OAuthAddUserPolicy")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) error); ok {
r0 = rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) error); ok {
r0 = rf(ctx, user)
} else {
r0 = ret.Error(0)
}
@@ -244,27 +245,27 @@ func (_m *Service) OAuthAddClientPolicy(ctx context.Context, client clients.Clie
return r0
}
// OAuthCallback provides a mock function with given fields: ctx, client
func (_m *Service) OAuthCallback(ctx context.Context, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, client)
// OAuthCallback provides a mock function with given fields: ctx, user
func (_m *Service) OAuthCallback(ctx context.Context, user users.User) (users.User, error) {
ret := _m.Called(ctx, user)
if len(ret) == 0 {
panic("no return value specified for OAuthCallback")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok {
return rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok {
return rf(ctx, user)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok {
r0 = rf(ctx, client)
if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok {
r0 = rf(ctx, user)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok {
r1 = rf(ctx, client)
if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok {
r1 = rf(ctx, user)
} else {
r1 = ret.Error(1)
}
@@ -302,27 +303,27 @@ func (_m *Service) RefreshToken(ctx context.Context, session authn.Session, refr
return r0, r1
}
// RegisterClient provides a mock function with given fields: ctx, session, client, selfRegister
func (_m *Service) RegisterClient(ctx context.Context, session authn.Session, client clients.Client, selfRegister bool) (clients.Client, error) {
ret := _m.Called(ctx, session, client, selfRegister)
// Register provides a mock function with given fields: ctx, session, user, selfRegister
func (_m *Service) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (users.User, error) {
ret := _m.Called(ctx, session, user, selfRegister)
if len(ret) == 0 {
panic("no return value specified for RegisterClient")
panic("no return value specified for Register")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client, bool) (clients.Client, error)); ok {
return rf(ctx, session, client, selfRegister)
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User, bool) (users.User, error)); ok {
return rf(ctx, session, user, selfRegister)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client, bool) clients.Client); ok {
r0 = rf(ctx, session, client, selfRegister)
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User, bool) users.User); ok {
r0 = rf(ctx, session, user, selfRegister)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, clients.Client, bool) error); ok {
r1 = rf(ctx, session, client, selfRegister)
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.User, bool) error); ok {
r1 = rf(ctx, session, user, selfRegister)
} else {
r1 = ret.Error(1)
}
@@ -349,25 +350,25 @@ func (_m *Service) ResetSecret(ctx context.Context, session authn.Session, secre
}
// SearchUsers provides a mock function with given fields: ctx, pm
func (_m *Service) SearchUsers(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
func (_m *Service) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) {
ret := _m.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for SearchUsers")
}
var r0 clients.ClientsPage
var r0 users.UsersPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) (clients.ClientsPage, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, users.Page) (users.UsersPage, error)); ok {
return rf(ctx, pm)
}
if rf, ok := ret.Get(0).(func(context.Context, clients.Page) clients.ClientsPage); ok {
if rf, ok := ret.Get(0).(func(context.Context, users.Page) users.UsersPage); ok {
r0 = rf(ctx, pm)
} else {
r0 = ret.Get(0).(clients.ClientsPage)
r0 = ret.Get(0).(users.UsersPage)
}
if rf, ok := ret.Get(1).(func(context.Context, clients.Page) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, users.Page) error); ok {
r1 = rf(ctx, pm)
} else {
r1 = ret.Error(1)
@@ -394,27 +395,27 @@ func (_m *Service) SendPasswordReset(ctx context.Context, host string, email str
return r0
}
// UpdateClient provides a mock function with given fields: ctx, session, client
func (_m *Service) UpdateClient(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, session, client)
// Update provides a mock function with given fields: ctx, session, user
func (_m *Service) Update(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
ret := _m.Called(ctx, session, user)
if len(ret) == 0 {
panic("no return value specified for UpdateClient")
panic("no return value specified for Update")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client) (clients.Client, error)); ok {
return rf(ctx, session, client)
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) (users.User, error)); ok {
return rf(ctx, session, user)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client) clients.Client); ok {
r0 = rf(ctx, session, client)
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) users.User); ok {
r0 = rf(ctx, session, user)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, clients.Client) error); ok {
r1 = rf(ctx, session, client)
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.User) error); ok {
r1 = rf(ctx, session, user)
} else {
r1 = ret.Error(1)
}
@@ -422,27 +423,27 @@ func (_m *Service) UpdateClient(ctx context.Context, session authn.Session, clie
return r0, r1
}
// UpdateClientIdentity provides a mock function with given fields: ctx, session, id, identity
func (_m *Service) UpdateClientIdentity(ctx context.Context, session authn.Session, id string, identity string) (clients.Client, error) {
ret := _m.Called(ctx, session, id, identity)
// UpdateEmail provides a mock function with given fields: ctx, session, id, email
func (_m *Service) UpdateEmail(ctx context.Context, session authn.Session, id string, email string) (users.User, error) {
ret := _m.Called(ctx, session, id, email)
if len(ret) == 0 {
panic("no return value specified for UpdateClientIdentity")
panic("no return value specified for UpdateEmail")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (clients.Client, error)); ok {
return rf(ctx, session, id, identity)
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (users.User, error)); ok {
return rf(ctx, session, id, email)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) clients.Client); ok {
r0 = rf(ctx, session, id, identity)
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) users.User); ok {
r0 = rf(ctx, session, id, email)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok {
r1 = rf(ctx, session, id, identity)
r1 = rf(ctx, session, id, email)
} else {
r1 = ret.Error(1)
}
@@ -450,27 +451,27 @@ func (_m *Service) UpdateClientIdentity(ctx context.Context, session authn.Sessi
return r0, r1
}
// UpdateClientRole provides a mock function with given fields: ctx, session, client
func (_m *Service) UpdateClientRole(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, session, client)
// UpdateProfilePicture provides a mock function with given fields: ctx, session, user
func (_m *Service) UpdateProfilePicture(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
ret := _m.Called(ctx, session, user)
if len(ret) == 0 {
panic("no return value specified for UpdateClientRole")
panic("no return value specified for UpdateProfilePicture")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client) (clients.Client, error)); ok {
return rf(ctx, session, client)
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) (users.User, error)); ok {
return rf(ctx, session, user)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client) clients.Client); ok {
r0 = rf(ctx, session, client)
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) users.User); ok {
r0 = rf(ctx, session, user)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, clients.Client) error); ok {
r1 = rf(ctx, session, client)
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.User) error); ok {
r1 = rf(ctx, session, user)
} else {
r1 = ret.Error(1)
}
@@ -478,23 +479,51 @@ func (_m *Service) UpdateClientRole(ctx context.Context, session authn.Session,
return r0, r1
}
// UpdateClientSecret provides a mock function with given fields: ctx, session, oldSecret, newSecret
func (_m *Service) UpdateClientSecret(ctx context.Context, session authn.Session, oldSecret string, newSecret string) (clients.Client, error) {
// UpdateRole provides a mock function with given fields: ctx, session, user
func (_m *Service) UpdateRole(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
ret := _m.Called(ctx, session, user)
if len(ret) == 0 {
panic("no return value specified for UpdateRole")
}
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) (users.User, error)); ok {
return rf(ctx, session, user)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) users.User); ok {
r0 = rf(ctx, session, user)
} else {
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.User) error); ok {
r1 = rf(ctx, session, user)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateSecret provides a mock function with given fields: ctx, session, oldSecret, newSecret
func (_m *Service) UpdateSecret(ctx context.Context, session authn.Session, oldSecret string, newSecret string) (users.User, error) {
ret := _m.Called(ctx, session, oldSecret, newSecret)
if len(ret) == 0 {
panic("no return value specified for UpdateClientSecret")
panic("no return value specified for UpdateSecret")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (clients.Client, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (users.User, error)); ok {
return rf(ctx, session, oldSecret, newSecret)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) clients.Client); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) users.User); ok {
r0 = rf(ctx, session, oldSecret, newSecret)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok {
@@ -506,27 +535,27 @@ func (_m *Service) UpdateClientSecret(ctx context.Context, session authn.Session
return r0, r1
}
// UpdateClientTags provides a mock function with given fields: ctx, session, client
func (_m *Service) UpdateClientTags(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) {
ret := _m.Called(ctx, session, client)
// UpdateTags provides a mock function with given fields: ctx, session, user
func (_m *Service) UpdateTags(ctx context.Context, session authn.Session, user users.User) (users.User, error) {
ret := _m.Called(ctx, session, user)
if len(ret) == 0 {
panic("no return value specified for UpdateClientTags")
panic("no return value specified for UpdateTags")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client) (clients.Client, error)); ok {
return rf(ctx, session, client)
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) (users.User, error)); ok {
return rf(ctx, session, user)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client) clients.Client); ok {
r0 = rf(ctx, session, client)
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) users.User); ok {
r0 = rf(ctx, session, user)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, clients.Client) error); ok {
r1 = rf(ctx, session, client)
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.User) error); ok {
r1 = rf(ctx, session, user)
} else {
r1 = ret.Error(1)
}
@@ -534,23 +563,51 @@ func (_m *Service) UpdateClientTags(ctx context.Context, session authn.Session,
return r0, r1
}
// ViewClient provides a mock function with given fields: ctx, session, id
func (_m *Service) ViewClient(ctx context.Context, session authn.Session, id string) (clients.Client, error) {
// UpdateUsername provides a mock function with given fields: ctx, session, id, username
func (_m *Service) UpdateUsername(ctx context.Context, session authn.Session, id string, username string) (users.User, error) {
ret := _m.Called(ctx, session, id, username)
if len(ret) == 0 {
panic("no return value specified for UpdateUsername")
}
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (users.User, error)); ok {
return rf(ctx, session, id, username)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) users.User); ok {
r0 = rf(ctx, session, id, username)
} else {
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok {
r1 = rf(ctx, session, id, username)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// View provides a mock function with given fields: ctx, session, id
func (_m *Service) View(ctx context.Context, session authn.Session, id string) (users.User, error) {
ret := _m.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for ViewClient")
panic("no return value specified for View")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (clients.Client, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (users.User, error)); ok {
return rf(ctx, session, id)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) clients.Client); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) users.User); ok {
r0 = rf(ctx, session, id)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
@@ -563,22 +620,22 @@ func (_m *Service) ViewClient(ctx context.Context, session authn.Session, id str
}
// ViewProfile provides a mock function with given fields: ctx, session
func (_m *Service) ViewProfile(ctx context.Context, session authn.Session) (clients.Client, error) {
func (_m *Service) ViewProfile(ctx context.Context, session authn.Session) (users.User, error) {
ret := _m.Called(ctx, session)
if len(ret) == 0 {
panic("no return value specified for ViewProfile")
}
var r0 clients.Client
var r0 users.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, authn.Session) (clients.Client, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session) (users.User, error)); ok {
return rf(ctx, session)
}
if rf, ok := ret.Get(0).(func(context.Context, authn.Session) clients.Client); ok {
if rf, ok := ret.Get(0).(func(context.Context, authn.Session) users.User); ok {
r0 = rf(ctx, session)
} else {
r0 = ret.Get(0).(clients.Client)
r0 = ret.Get(0).(users.User)
}
if rf, ok := ret.Get(1).(func(context.Context, authn.Session) error); ok {
-203
View File
@@ -1,203 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"context"
"fmt"
mgclients "github.com/absmach/magistrala/pkg/clients"
pgclients "github.com/absmach/magistrala/pkg/clients/postgres"
"github.com/absmach/magistrala/pkg/errors"
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
"github.com/absmach/magistrala/pkg/postgres"
)
var _ mgclients.Repository = (*clientRepo)(nil)
type clientRepo struct {
pgclients.Repository
}
// Repository defines the required dependencies for Client repository.
//
//go:generate mockery --name Repository --output=../mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines"
type Repository interface {
mgclients.Repository
// Save persists the client account. A non-nil error is returned to indicate
// operation failure.
Save(ctx context.Context, client mgclients.Client) (mgclients.Client, error)
RetrieveByID(ctx context.Context, id string) (mgclients.Client, error)
UpdateRole(ctx context.Context, client mgclients.Client) (mgclients.Client, error)
CheckSuperAdmin(ctx context.Context, adminID string) error
}
// NewRepository instantiates a PostgreSQL
// implementation of Clients repository.
func NewRepository(db postgres.Database) Repository {
return &clientRepo{
Repository: pgclients.Repository{DB: db},
}
}
func (repo clientRepo) Save(ctx context.Context, c mgclients.Client) (mgclients.Client, error) {
q := `INSERT INTO clients (id, name, tags, identity, secret, metadata, created_at, status, role)
VALUES (:id, :name, :tags, :identity, :secret, :metadata, :created_at, :status, :role)
RETURNING id, name, tags, identity, metadata, status, created_at`
dbc, err := pgclients.ToDBClient(c)
if err != nil {
return mgclients.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err)
}
row, err := repo.DB.NamedQueryContext(ctx, q, dbc)
if err != nil {
return mgclients.Client{}, postgres.HandleError(repoerr.ErrCreateEntity, err)
}
defer row.Close()
row.Next()
dbc = pgclients.DBClient{}
if err := row.StructScan(&dbc); err != nil {
return mgclients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err)
}
client, err := pgclients.ToClient(dbc)
if err != nil {
return mgclients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err)
}
return client, nil
}
func (repo clientRepo) CheckSuperAdmin(ctx context.Context, adminID string) error {
q := "SELECT 1 FROM clients WHERE id = $1 AND role = $2"
rows, err := repo.DB.QueryContext(ctx, q, adminID, mgclients.AdminRole)
if err != nil {
return postgres.HandleError(repoerr.ErrViewEntity, err)
}
defer rows.Close()
if rows.Next() {
if err := rows.Err(); err != nil {
return postgres.HandleError(repoerr.ErrViewEntity, err)
}
return nil
}
return repoerr.ErrNotFound
}
func (repo clientRepo) RetrieveByID(ctx context.Context, id string) (mgclients.Client, error) {
q := `SELECT id, name, tags, identity, secret, metadata, created_at, updated_at, updated_by, status, role
FROM clients WHERE id = :id`
dbc := pgclients.DBClient{
ID: id,
}
rows, err := repo.DB.NamedQueryContext(ctx, q, dbc)
if err != nil {
return mgclients.Client{}, postgres.HandleError(repoerr.ErrViewEntity, err)
}
defer rows.Close()
dbc = pgclients.DBClient{}
if rows.Next() {
if err = rows.StructScan(&dbc); err != nil {
return mgclients.Client{}, postgres.HandleError(repoerr.ErrViewEntity, err)
}
client, err := pgclients.ToClient(dbc)
if err != nil {
return mgclients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err)
}
return client, nil
}
return mgclients.Client{}, repoerr.ErrNotFound
}
func (repo clientRepo) RetrieveAll(ctx context.Context, pm mgclients.Page) (mgclients.ClientsPage, error) {
query, err := pgclients.PageQuery(pm)
if err != nil {
return mgclients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, c.status, c.role,
c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, query)
dbPage, err := pgclients.ToDBClientsPage(pm)
if err != nil {
return mgclients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err)
}
rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage)
if err != nil {
return mgclients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err)
}
defer rows.Close()
var items []mgclients.Client
for rows.Next() {
dbc := pgclients.DBClient{}
if err := rows.StructScan(&dbc); err != nil {
return mgclients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
c, err := pgclients.ToClient(dbc)
if err != nil {
return mgclients.ClientsPage{}, err
}
items = append(items, c)
}
cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c %s;`, query)
total, err := postgres.Total(ctx, repo.DB, cq, dbPage)
if err != nil {
return mgclients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
page := mgclients.ClientsPage{
Clients: items,
Page: mgclients.Page{
Total: total,
Offset: pm.Offset,
Limit: pm.Limit,
},
}
return page, nil
}
func (repo clientRepo) UpdateRole(ctx context.Context, client mgclients.Client) (mgclients.Client, error) {
query := `UPDATE clients SET role = :role, updated_at = :updated_at, updated_by = :updated_by
WHERE id = :id AND status = :status
RETURNING id, name, tags, identity, metadata, status, role, created_at, updated_at, updated_by`
dbc, err := pgclients.ToDBClient(client)
if err != nil {
return mgclients.Client{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
row, err := repo.DB.NamedQueryContext(ctx, query, dbc)
if err != nil {
return mgclients.Client{}, postgres.HandleError(err, repoerr.ErrUpdateEntity)
}
defer row.Close()
if ok := row.Next(); !ok {
return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, row.Err())
}
dbc = pgclients.DBClient{}
if err := row.StructScan(&dbc); err != nil {
return mgclients.Client{}, err
}
return pgclients.ToClient(dbc)
}
-754
View File
@@ -1,754 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package postgres_test
import (
"context"
"fmt"
"strings"
"testing"
"github.com/0x6flab/namegenerator"
"github.com/absmach/magistrala/internal/testsutil"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/pkg/errors"
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
cpostgres "github.com/absmach/magistrala/users/postgres"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const maxNameSize = 254
var (
invalidName = strings.Repeat("m", maxNameSize+10)
password = "$tr0ngPassw0rd"
namesgen = namegenerator.NewGenerator()
)
func TestClientsSave(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := cpostgres.NewRepository(database)
uid := testsutil.GenerateUUID(t)
name := namesgen.Generate()
clientIdentity := name + "@example.com"
cases := []struct {
desc string
client mgclients.Client
err error
}{
{
desc: "add new client successfully",
client: mgclients.Client{
ID: uid,
Name: name,
Credentials: mgclients.Credentials{
Identity: clientIdentity,
Secret: password,
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
},
err: nil,
},
{
desc: "add client with duplicate client identity",
client: mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: namesgen.Generate(),
Credentials: mgclients.Credentials{
Identity: clientIdentity,
Secret: password,
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
},
err: repoerr.ErrConflict,
},
{
desc: "add client with duplicate client name",
client: mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: name,
Credentials: mgclients.Credentials{
Identity: clientIdentity,
Secret: password,
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
},
err: repoerr.ErrConflict,
},
{
desc: "add client with invalid client id",
client: mgclients.Client{
ID: invalidName,
Name: namesgen.Generate(),
Credentials: mgclients.Credentials{
Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()),
Secret: password,
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
},
err: errors.ErrMalformedEntity,
},
{
desc: "add client with invalid client name",
client: mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: invalidName,
Credentials: mgclients.Credentials{
Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()),
Secret: password,
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
},
err: errors.ErrMalformedEntity,
},
{
desc: "add client with invalid client identity",
client: mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: namesgen.Generate(),
Credentials: mgclients.Credentials{
Identity: invalidName,
Secret: password,
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
},
err: errors.ErrMalformedEntity,
},
{
desc: "add client with a missing client name",
client: mgclients.Client{
ID: testsutil.GenerateUUID(t),
Credentials: mgclients.Credentials{
Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()),
Secret: password,
},
Metadata: mgclients.Metadata{},
},
err: nil,
},
{
desc: "add client with a missing client identity",
client: mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: namesgen.Generate(),
Credentials: mgclients.Credentials{
Secret: password,
},
Metadata: mgclients.Metadata{},
},
err: nil,
},
{
desc: "add client with a missing client secret",
client: mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: namesgen.Generate(),
Credentials: mgclients.Credentials{
Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()),
},
Metadata: mgclients.Metadata{},
},
err: nil,
},
{
desc: "add a client with invalid metadata",
client: mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: namesgen.Generate(),
Credentials: mgclients.Credentials{
Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()),
Secret: password,
},
Metadata: map[string]interface{}{
"key": make(chan int),
},
},
err: errors.ErrMalformedEntity,
},
}
for _, tc := range cases {
rClient, err := repo.Save(context.Background(), tc.client)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
rClient.Credentials.Secret = tc.client.Credentials.Secret
assert.Equal(t, tc.client, rClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client, rClient))
}
}
}
func TestIsPlatformAdmin(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := cpostgres.NewRepository(database)
cases := []struct {
desc string
client mgclients.Client
err error
}{
{
desc: "authorize check for super user",
client: mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: namesgen.Generate(),
Credentials: mgclients.Credentials{
Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()),
Secret: password,
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
Role: mgclients.AdminRole,
},
err: nil,
},
{
desc: "unauthorize user",
client: mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: namesgen.Generate(),
Credentials: mgclients.Credentials{
Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()),
Secret: password,
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
Role: mgclients.UserRole,
},
err: repoerr.ErrNotFound,
},
}
for _, tc := range cases {
_, err := repo.Save(context.Background(), tc.client)
require.Nil(t, err, fmt.Sprintf("%s: save client unexpected error: %s", tc.desc, err))
err = repo.CheckSuperAdmin(context.Background(), tc.client.ID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err))
}
}
func TestRetrieveByID(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := cpostgres.NewRepository(database)
client := mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: namesgen.Generate(),
Credentials: mgclients.Credentials{
Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()),
Secret: password,
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
}
_, err := repo.Save(context.Background(), client)
require.Nil(t, err, fmt.Sprintf("failed to save client %s", client.ID))
cases := []struct {
desc string
clientID string
err error
}{
{
desc: "retrieve existing client",
clientID: client.ID,
err: nil,
},
{
desc: "retrieve non-existing client",
clientID: invalidName,
err: repoerr.ErrNotFound,
},
{
desc: "retrieve with empty client id",
clientID: "",
err: repoerr.ErrNotFound,
},
}
for _, tc := range cases {
_, err := repo.RetrieveByID(context.Background(), tc.clientID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err))
}
}
func TestRetrieveAll(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := cpostgres.NewRepository(database)
num := 200
var items, enabledClients []mgclients.Client
for i := 0; i < num; i++ {
client := mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: namesgen.Generate(),
Credentials: mgclients.Credentials{
Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()),
Secret: "",
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
Tags: []string{"tag1"},
}
if i%50 == 0 {
client.Metadata = map[string]interface{}{
"key": "value",
}
client.Role = mgclients.AdminRole
client.Status = mgclients.DisabledStatus
}
_, err := repo.Save(context.Background(), client)
require.Nil(t, err, fmt.Sprintf("failed to save client %s", client.ID))
items = append(items, client)
if client.Status == mgclients.EnabledStatus {
enabledClients = append(enabledClients, client)
}
}
cases := []struct {
desc string
pageMeta mgclients.Page
page mgclients.ClientsPage
err error
}{
{
desc: "retrieve first page of clients",
pageMeta: mgclients.Page{
Offset: 0,
Limit: 50,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 200,
Offset: 0,
Limit: 50,
},
Clients: items[0:50],
},
err: nil,
},
{
desc: "retrieve second page of clients",
pageMeta: mgclients.Page{
Offset: 50,
Limit: 200,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 200,
Offset: 50,
Limit: 200,
},
Clients: items[50:200],
},
err: nil,
},
{
desc: "retrieve clients with limit",
pageMeta: mgclients.Page{
Offset: 0,
Limit: 50,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: uint64(num),
Offset: 0,
Limit: 50,
},
Clients: items[:50],
},
},
{
desc: "retrieve with offset out of range",
pageMeta: mgclients.Page{
Offset: 1000,
Limit: 200,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 200,
Offset: 1000,
Limit: 200,
},
Clients: []mgclients.Client{},
},
err: nil,
},
{
desc: "retrieve with limit out of range",
pageMeta: mgclients.Page{
Offset: 0,
Limit: 1000,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 200,
Offset: 0,
Limit: 1000,
},
Clients: items,
},
err: nil,
},
{
desc: "retrieve with empty page",
pageMeta: mgclients.Page{},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 196, // No of enabled clients.
Offset: 0,
Limit: 0,
},
Clients: []mgclients.Client{},
},
err: nil,
},
{
desc: "retrieve with client id",
pageMeta: mgclients.Page{
IDs: []string{items[0].ID},
Offset: 0,
Limit: 3,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 1,
Offset: 0,
Limit: 3,
},
Clients: []mgclients.Client{items[0]},
},
err: nil,
},
{
desc: "retrieve with invalid client id",
pageMeta: mgclients.Page{
IDs: []string{invalidName},
Offset: 0,
Limit: 3,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 0,
Offset: 0,
Limit: 3,
},
Clients: []mgclients.Client{},
},
err: nil,
},
{
desc: "retrieve with client name",
pageMeta: mgclients.Page{
Name: items[0].Name,
Offset: 0,
Limit: 3,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 1,
Offset: 0,
Limit: 3,
},
Clients: []mgclients.Client{items[0]},
},
err: nil,
},
{
desc: "retrieve with enabled status",
pageMeta: mgclients.Page{
Status: mgclients.EnabledStatus,
Offset: 0,
Limit: 200,
Role: mgclients.AllRole,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 196,
Offset: 0,
Limit: 200,
},
Clients: enabledClients,
},
err: nil,
},
{
desc: "retrieve with disabled status",
pageMeta: mgclients.Page{
Status: mgclients.DisabledStatus,
Offset: 0,
Limit: 200,
Role: mgclients.AllRole,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 4,
Offset: 0,
Limit: 200,
},
Clients: []mgclients.Client{items[0], items[50], items[100], items[150]},
},
},
{
desc: "retrieve with all status",
pageMeta: mgclients.Page{
Status: mgclients.AllStatus,
Offset: 0,
Limit: 200,
Role: mgclients.AllRole,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 200,
Offset: 0,
Limit: 200,
},
Clients: items,
},
},
{
desc: "retrieve by tags",
pageMeta: mgclients.Page{
Tag: "tag1",
Offset: 0,
Limit: 200,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 200,
Offset: 0,
Limit: 200,
},
Clients: items,
},
err: nil,
},
{
desc: "retrieve with invalid client name",
pageMeta: mgclients.Page{
Name: invalidName,
Offset: 0,
Limit: 3,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 0,
Offset: 0,
Limit: 3,
},
Clients: []mgclients.Client{},
},
},
{
desc: "retrieve with metadata",
pageMeta: mgclients.Page{
Metadata: map[string]interface{}{
"key": "value",
},
Offset: 0,
Limit: 200,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 4,
Offset: 0,
Limit: 200,
},
Clients: []mgclients.Client{items[0], items[50], items[100], items[150]},
},
err: nil,
},
{
desc: "retrieve with invalid metadata",
pageMeta: mgclients.Page{
Metadata: map[string]interface{}{
"key": "value1",
},
Offset: 0,
Limit: 200,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 0,
Offset: 0,
Limit: 200,
},
Clients: []mgclients.Client{},
},
err: nil,
},
{
desc: "retrieve with role",
pageMeta: mgclients.Page{
Role: mgclients.AdminRole,
Offset: 0,
Limit: 200,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 4,
Offset: 0,
Limit: 200,
},
Clients: []mgclients.Client{items[0], items[50], items[100], items[150]},
},
err: nil,
},
{
desc: "retrieve with invalid role",
pageMeta: mgclients.Page{
Role: mgclients.AdminRole + 2,
Offset: 0,
Limit: 200,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 0,
Offset: 0,
Limit: 200,
},
Clients: []mgclients.Client{},
},
err: nil,
},
{
desc: "retrieve with identity",
pageMeta: mgclients.Page{
Identity: items[0].Credentials.Identity,
Offset: 0,
Limit: 3,
Role: mgclients.AllRole,
Status: mgclients.AllStatus,
},
page: mgclients.ClientsPage{
Page: mgclients.Page{
Total: 1,
Offset: 0,
Limit: 3,
},
Clients: []mgclients.Client{items[0]},
},
err: nil,
},
}
for _, tc := range cases {
page, err := repo.RetrieveAll(context.Background(), tc.pageMeta)
assert.Equal(t, tc.page.Total, page.Total, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Total, page.Total))
assert.Equal(t, tc.page.Offset, page.Offset, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Offset, page.Offset))
assert.Equal(t, tc.page.Limit, page.Limit, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Limit, page.Limit))
assert.Equal(t, tc.page.Page, page.Page, fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.page, page))
assert.ElementsMatch(t, tc.page.Clients, page.Clients, fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.page.Clients, page.Clients))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateRole(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM clients")
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
})
repo := cpostgres.NewRepository(database)
client := mgclients.Client{
ID: testsutil.GenerateUUID(t),
Name: namesgen.Generate(),
Credentials: mgclients.Credentials{
Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()),
Secret: password,
},
Metadata: mgclients.Metadata{},
Status: mgclients.EnabledStatus,
Role: mgclients.UserRole,
}
_, err := repo.Save(context.Background(), client)
require.Nil(t, err, fmt.Sprintf("failed to save client %s", client.ID))
cases := []struct {
desc string
client mgclients.Client
newRole mgclients.Role
err error
}{
{
desc: "update role to admin",
client: client,
newRole: mgclients.AdminRole,
err: nil,
},
{
desc: "update role to user",
client: client,
newRole: mgclients.UserRole,
err: nil,
},
{
desc: "update role with invalid client id",
client: mgclients.Client{ID: invalidName},
newRole: mgclients.AdminRole,
err: repoerr.ErrNotFound,
},
}
for _, tc := range cases {
tc.client.Role = tc.newRole
client, err := repo.UpdateRole(context.Background(), tc.client)
if err != nil {
assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected error %v, got %v", tc.desc, tc.err, err))
} else {
assert.Equal(t, tc.newRole, client.Role, fmt.Sprintf("%s: expected role %v, got %v", tc.desc, tc.newRole, client.Role))
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package postgres contains the database implementation of clients repository layer.
// Package postgres contains the database implementation of users repository layer.
package postgres
+41
View File
@@ -45,6 +45,47 @@ func Migration() *migrate.MemoryMigrationSource {
},
Down: []string{},
},
{
Id: "clients_03",
Up: []string{
`ALTER TABLE clients
ADD COLUMN username VARCHAR(254) UNIQUE,
ADD COLUMN first_name VARCHAR(254) NOT NULL DEFAULT '',
ADD COLUMN last_name VARCHAR(254) NOT NULL DEFAULT '',
ADD COLUMN profile_picture TEXT`,
`ALTER TABLE clients RENAME COLUMN identity TO email`,
`ALTER TABLE clients DROP COLUMN name`,
},
Down: []string{
`ALTER TABLE clients
DROP COLUMN username,
DROP COLUMN first_name,
DROP COLUMN last_name,
DROP COLUMN profile_picture`,
`ALTER TABLE clients RENAME COLUMN email TO identity`,
`ALTER TABLE clients ADD COLUMN name VARCHAR(254) NOT NULL UNIQUE`,
},
},
{
Id: "clients_04",
Up: []string{
`ALTER TABLE IF EXISTS clients RENAME TO users`,
},
Down: []string{
`ALTER TABLE IF EXISTS users RENAME TO clients`,
},
},
{
Id: "clients_05",
Up: []string{
`ALTER TABLE users ALTER COLUMN first_name DROP DEFAULT`,
`ALTER TABLE users ALTER COLUMN last_name DROP DEFAULT`,
},
Down: []string{
`ALTER TABLE users ALTER COLUMN first_name SET DEFAULT ''`,
`ALTER TABLE users ALTER COLUMN last_name SET DEFAULT ''`,
},
},
},
}
}
+2 -3
View File
@@ -11,7 +11,6 @@ import (
"testing"
"time"
"github.com/absmach/magistrala/pkg/postgres"
pgclient "github.com/absmach/magistrala/pkg/postgres"
upostgres "github.com/absmach/magistrala/users/postgres"
"github.com/jmoiron/sqlx"
@@ -22,7 +21,7 @@ import (
var (
db *sqlx.DB
database postgres.Database
database pgclient.Database
tracer = otel.Tracer("repo_tests")
)
@@ -80,7 +79,7 @@ func TestMain(m *testing.M) {
log.Fatalf("Could not setup test DB connection: %s", err)
}
database = postgres.NewDatabase(db, dbConfig, tracer)
database = pgclient.NewDatabase(db, dbConfig, tracer)
code := m.Run()
+684
View File
@@ -0,0 +1,684 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/absmach/magistrala/internal/api"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/pkg/errors"
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
"github.com/absmach/magistrala/pkg/groups"
"github.com/absmach/magistrala/pkg/postgres"
"github.com/absmach/magistrala/users"
"github.com/jackc/pgtype"
)
type userRepo struct {
Repository users.UserRepository
}
func NewRepository(db postgres.Database) users.Repository {
return &userRepo{
Repository: users.UserRepository{DB: db},
}
}
func (repo *userRepo) Save(ctx context.Context, c users.User) (users.User, error) {
q := `INSERT INTO users (id, tags, email, secret, metadata, created_at, status, role, first_name, last_name, username, profile_picture)
VALUES (:id, :tags, :email, :secret, :metadata, :created_at, :status, :role, :first_name, :last_name, :username, :profile_picture)
RETURNING id, tags, email, metadata, created_at, status, first_name, last_name, username, profile_picture`
dbu, err := toDBUser(c)
if err != nil {
return users.User{}, errors.Wrap(repoerr.ErrCreateEntity, err)
}
row, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbu)
if err != nil {
return users.User{}, postgres.HandleError(repoerr.ErrCreateEntity, err)
}
defer row.Close()
row.Next()
dbu = DBUser{}
if err := row.StructScan(&dbu); err != nil {
return users.User{}, errors.Wrap(repoerr.ErrFailedOpDB, err)
}
user, err := ToUser(dbu)
if err != nil {
return users.User{}, errors.Wrap(repoerr.ErrFailedOpDB, err)
}
return user, nil
}
func (repo *userRepo) CheckSuperAdmin(ctx context.Context, adminID string) error {
q := "SELECT 1 FROM users WHERE id = $1 AND role = $2"
rows, err := repo.Repository.DB.QueryContext(ctx, q, adminID, mgclients.AdminRole)
if err != nil {
return postgres.HandleError(repoerr.ErrViewEntity, err)
}
defer rows.Close()
if rows.Next() {
if err := rows.Err(); err != nil {
return postgres.HandleError(repoerr.ErrViewEntity, err)
}
return nil
}
return repoerr.ErrNotFound
}
func (repo *userRepo) RetrieveByID(ctx context.Context, id string) (users.User, error) {
q := `SELECT id, tags, email, secret, metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username, profile_picture
FROM users WHERE id = :id`
dbu := DBUser{
ID: id,
}
rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbu)
if err != nil {
return users.User{}, postgres.HandleError(repoerr.ErrViewEntity, err)
}
defer rows.Close()
dbu = DBUser{}
if rows.Next() {
if err = rows.StructScan(&dbu); err != nil {
return users.User{}, postgres.HandleError(repoerr.ErrViewEntity, err)
}
user, err := ToUser(dbu)
if err != nil {
return users.User{}, errors.Wrap(repoerr.ErrFailedOpDB, err)
}
return user, nil
}
return users.User{}, repoerr.ErrNotFound
}
func (repo *userRepo) RetrieveAll(ctx context.Context, pm users.Page) (users.UsersPage, error) {
query, err := PageQuery(pm)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
q := fmt.Sprintf(`SELECT u.id, u.tags, u.email, u.metadata, u.status, u.role, u.first_name, u.last_name, u.username,
u.created_at, u.updated_at, u.profile_picture, COALESCE(u.updated_by, '') AS updated_by
FROM users u %s ORDER BY u.created_at LIMIT :limit OFFSET :offset;`, query)
dbPage, err := ToDBUsersPage(pm)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err)
}
rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbPage)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err)
}
defer rows.Close()
var items []users.User
for rows.Next() {
dbu := DBUser{}
if err := rows.StructScan(&dbu); err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
c, err := ToUser(dbu)
if err != nil {
return users.UsersPage{}, err
}
items = append(items, c)
}
cq := fmt.Sprintf(`SELECT COUNT(*) FROM users u %s;`, query)
total, err := postgres.Total(ctx, repo.Repository.DB, cq, dbPage)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
page := users.UsersPage{
Page: users.Page{
Total: total,
Offset: pm.Offset,
Limit: pm.Limit,
},
Users: items,
}
return page, nil
}
func (repo *userRepo) UpdateUsername(ctx context.Context, user users.User) (users.User, error) {
q := `UPDATE users SET username = :username, updated_at = :updated_at, updated_by = :updated_by
WHERE id = :id AND status = :status
RETURNING id, tags, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, email`
dbu, err := toDBUser(user)
if err != nil {
return users.User{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
row, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbu)
if err != nil {
return users.User{}, postgres.HandleError(err, repoerr.ErrUpdateEntity)
}
defer row.Close()
dbu = DBUser{
ID: user.ID,
Username: stringToNullString(user.Credentials.Username),
UpdatedAt: sql.NullTime{Time: time.Now(), Valid: true},
}
if ok := row.Next(); !ok {
return users.User{}, errors.Wrap(repoerr.ErrNotFound, row.Err())
}
if err := row.StructScan(&dbu); err != nil {
return users.User{}, err
}
return ToUser(dbu)
}
func (repo *userRepo) Update(ctx context.Context, user users.User) (users.User, error) {
var query []string
var upq string
if user.FirstName != "" {
query = append(query, "first_name = :first_name,")
}
if user.LastName != "" {
query = append(query, "last_name = :last_name,")
}
if user.Metadata != nil {
query = append(query, "metadata = :metadata,")
}
if len(user.Tags) > 0 {
query = append(query, "tags = :tags,")
}
if user.Role != users.AllRole {
query = append(query, "role = :role,")
}
if user.ProfilePicture != "" {
query = append(query, "profile_picture = :profile_picture,")
}
if user.Email != "" {
query = append(query, "email = :email,")
}
if len(query) > 0 {
upq = strings.Join(query, " ")
}
q := fmt.Sprintf(`UPDATE users SET %s updated_at = :updated_at, updated_by = :updated_by
WHERE id = :id AND status = :status
RETURNING id, tags, metadata, status, created_at, updated_at, updated_by, last_name, first_name, username, profile_picture, email`, upq)
user.Status = users.EnabledStatus
return repo.update(ctx, user, q)
}
func (repo *userRepo) update(ctx context.Context, user users.User, query string) (users.User, error) {
dbu, err := toDBUser(user)
if err != nil {
return users.User{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
row, err := repo.Repository.DB.NamedQueryContext(ctx, query, dbu)
if err != nil {
return users.User{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
}
defer row.Close()
dbu = DBUser{}
if row.Next() {
if err := row.StructScan(&dbu); err != nil {
return users.User{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
return ToUser(dbu)
}
return users.User{}, repoerr.ErrNotFound
}
func (repo *userRepo) UpdateSecret(ctx context.Context, user users.User) (users.User, error) {
q := `UPDATE users SET secret = :secret, updated_at = :updated_at, updated_by = :updated_by
WHERE id = :id AND status = :status
RETURNING id, tags, email, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username`
user.Status = users.EnabledStatus
return repo.update(ctx, user, q)
}
func (repo *userRepo) ChangeStatus(ctx context.Context, user users.User) (users.User, error) {
q := `UPDATE users SET status = :status, updated_at = :updated_at, updated_by = :updated_by
WHERE id = :id
RETURNING id, tags, email, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username`
return repo.update(ctx, user, q)
}
func (repo *userRepo) Delete(ctx context.Context, id string) error {
q := "DELETE FROM users AS u WHERE u.id = $1 ;"
result, err := repo.Repository.DB.ExecContext(ctx, q, id)
if err != nil {
return postgres.HandleError(repoerr.ErrRemoveEntity, err)
}
if rows, _ := result.RowsAffected(); rows == 0 {
return repoerr.ErrNotFound
}
return nil
}
func (repo *userRepo) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) {
query, err := PageQuery(pm)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
tq := query
query = applyOrdering(query, pm)
q := fmt.Sprintf(`SELECT u.id, u.username, u.first_name, u.last_name, u.created_at, u.updated_at FROM users u %s LIMIT :limit OFFSET :offset;`, query)
dbPage, err := ToDBUsersPage(pm)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err)
}
rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbPage)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err)
}
defer rows.Close()
var items []users.User
for rows.Next() {
dbu := DBUser{}
if err := rows.StructScan(&dbu); err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
c, err := ToUser(dbu)
if err != nil {
return users.UsersPage{}, err
}
items = append(items, c)
}
cq := fmt.Sprintf(`SELECT COUNT(*) FROM users u %s;`, tq)
total, err := postgres.Total(ctx, repo.Repository.DB, cq, dbPage)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
page := users.UsersPage{
Users: items,
Page: users.Page{
Total: total,
Offset: pm.Offset,
Limit: pm.Limit,
},
}
return page, nil
}
func (repo *userRepo) RetrieveAllByIDs(ctx context.Context, pm users.Page) (users.UsersPage, error) {
if (len(pm.IDs) == 0) && (pm.Domain == "") {
return users.UsersPage{
Page: users.Page{Total: pm.Total, Offset: pm.Offset, Limit: pm.Limit},
}, nil
}
query, err := PageQuery(pm)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
query = applyOrdering(query, pm)
q := fmt.Sprintf(`SELECT u.id, u.username, u.tags, u.email, u.metadata, u.status, u.role, u.first_name, u.last_name,
u.created_at, u.updated_at, COALESCE(u.updated_by, '') AS updated_by FROM users u %s ORDER BY u.created_at LIMIT :limit OFFSET :offset;`, query)
dbPage, err := ToDBUsersPage(pm)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err)
}
rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbPage)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err)
}
defer rows.Close()
var items []users.User
for rows.Next() {
dbu := DBUser{}
if err := rows.StructScan(&dbu); err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
c, err := ToUser(dbu)
if err != nil {
return users.UsersPage{}, err
}
items = append(items, c)
}
cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c %s;`, query)
total, err := postgres.Total(ctx, repo.Repository.DB, cq, dbPage)
if err != nil {
return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
page := users.UsersPage{
Users: items,
Page: users.Page{
Total: total,
Offset: pm.Offset,
Limit: pm.Limit,
},
}
return page, nil
}
func (repo *userRepo) RetrieveByEmail(ctx context.Context, email string) (users.User, error) {
q := `SELECT id, tags, email, secret, metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username
FROM users WHERE email = :email AND status = :status`
dbu := DBUser{
Email: email,
Status: users.EnabledStatus,
}
row, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbu)
if err != nil {
return users.User{}, postgres.HandleError(repoerr.ErrViewEntity, err)
}
defer row.Close()
dbu = DBUser{}
if row.Next() {
if err := row.StructScan(&dbu); err != nil {
return users.User{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
return ToUser(dbu)
}
return users.User{}, repoerr.ErrNotFound
}
func (repo *userRepo) RetrieveByUsername(ctx context.Context, username string) (users.User, error) {
q := `SELECT id, tags, email, secret, metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username
FROM users WHERE username = :username AND status = :status`
dbu := DBUser{
Username: sql.NullString{String: username, Valid: username != ""},
Status: users.EnabledStatus,
}
row, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbu)
if err != nil {
return users.User{}, postgres.HandleError(repoerr.ErrViewEntity, err)
}
defer row.Close()
dbu = DBUser{}
if row.Next() {
if err := row.StructScan(&dbu); err != nil {
return users.User{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
return ToUser(dbu)
}
return users.User{}, repoerr.ErrNotFound
}
type DBUser struct {
ID string `db:"id"`
Domain string `db:"domain_id"`
Secret string `db:"secret"`
Metadata []byte `db:"metadata,omitempty"`
Tags pgtype.TextArray `db:"tags,omitempty"` // Tags
CreatedAt time.Time `db:"created_at,omitempty"`
UpdatedAt sql.NullTime `db:"updated_at,omitempty"`
UpdatedBy *string `db:"updated_by,omitempty"`
Groups []groups.Group `db:"groups,omitempty"`
Status users.Status `db:"status,omitempty"`
Role *users.Role `db:"role,omitempty"`
Username sql.NullString `db:"username, omitempty"`
FirstName sql.NullString `db:"first_name, omitempty"`
LastName sql.NullString `db:"last_name, omitempty"`
ProfilePicture sql.NullString `db:"profile_picture, omitempty"`
Email string `db:"email,omitempty"`
}
func toDBUser(u users.User) (DBUser, error) {
data := []byte("{}")
if len(u.Metadata) > 0 {
b, err := json.Marshal(u.Metadata)
if err != nil {
return DBUser{}, errors.Wrap(repoerr.ErrMalformedEntity, err)
}
data = b
}
var tags pgtype.TextArray
if err := tags.Set(u.Tags); err != nil {
return DBUser{}, err
}
var updatedBy *string
if u.UpdatedBy != "" {
updatedBy = &u.UpdatedBy
}
var updatedAt sql.NullTime
if u.UpdatedAt != (time.Time{}) {
updatedAt = sql.NullTime{Time: u.UpdatedAt, Valid: true}
}
return DBUser{
ID: u.ID,
Tags: tags,
Secret: u.Credentials.Secret,
Metadata: data,
CreatedAt: u.CreatedAt,
UpdatedAt: updatedAt,
UpdatedBy: updatedBy,
Status: u.Status,
Role: &u.Role,
LastName: stringToNullString(u.LastName),
FirstName: stringToNullString(u.FirstName),
Username: stringToNullString(u.Credentials.Username),
ProfilePicture: stringToNullString(u.ProfilePicture),
Email: u.Email,
}, nil
}
func ToUser(dbu DBUser) (users.User, error) {
var metadata users.Metadata
if dbu.Metadata != nil {
if err := json.Unmarshal([]byte(dbu.Metadata), &metadata); err != nil {
return users.User{}, errors.Wrap(repoerr.ErrMalformedEntity, err)
}
}
var tags []string
for _, e := range dbu.Tags.Elements {
tags = append(tags, e.String)
}
var updatedBy string
if dbu.UpdatedBy != nil {
updatedBy = *dbu.UpdatedBy
}
var updatedAt time.Time
if dbu.UpdatedAt.Valid {
updatedAt = dbu.UpdatedAt.Time
}
user := users.User{
ID: dbu.ID,
FirstName: nullStringString(dbu.FirstName),
LastName: nullStringString(dbu.LastName),
Credentials: users.Credentials{
Username: nullStringString(dbu.Username),
Secret: dbu.Secret,
},
Email: dbu.Email,
Metadata: metadata,
CreatedAt: dbu.CreatedAt,
UpdatedAt: updatedAt,
UpdatedBy: updatedBy,
Status: dbu.Status,
Tags: tags,
ProfilePicture: nullStringString(dbu.ProfilePicture),
}
if dbu.Role != nil {
user.Role = *dbu.Role
}
return user, nil
}
type DBUsersPage struct {
Total uint64 `db:"total"`
Limit uint64 `db:"limit"`
Offset uint64 `db:"offset"`
FirstName string `db:"first_name"`
LastName string `db:"last_name"`
Username string `db:"username"`
Id string `db:"id"`
Email string `db:"email"`
Metadata []byte `db:"metadata"`
Tag string `db:"tag"`
GroupID string `db:"group_id"`
Role users.Role `db:"role"`
Status users.Status `db:"status"`
}
func ToDBUsersPage(pm users.Page) (DBUsersPage, error) {
_, data, err := postgres.CreateMetadataQuery("", pm.Metadata)
if err != nil {
return DBUsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
return DBUsersPage{
FirstName: pm.FirstName,
LastName: pm.LastName,
Username: pm.Username,
Email: pm.Email,
Id: pm.Id,
Metadata: data,
Total: pm.Total,
Offset: pm.Offset,
Limit: pm.Limit,
Status: pm.Status,
Tag: pm.Tag,
Role: pm.Role,
}, nil
}
func PageQuery(pm users.Page) (string, error) {
mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata)
if err != nil {
return "", errors.Wrap(errors.ErrMalformedEntity, err)
}
var query []string
if pm.FirstName != "" {
query = append(query, "first_name ILIKE '%' || :first_name || '%'")
}
if pm.LastName != "" {
query = append(query, "last_name ILIKE '%' || :last_name || '%'")
}
if pm.Username != "" {
query = append(query, "username ILIKE '%' || :username || '%'")
}
if pm.Email != "" {
query = append(query, "email ILIKE '%' || :email || '%'")
}
if pm.Id != "" {
query = append(query, "id ILIKE '%' || :id || '%'")
}
if pm.Tag != "" {
query = append(query, "EXISTS (SELECT 1 FROM unnest(tags) AS tag WHERE tag ILIKE '%' || :tag || '%')")
}
if pm.Role != users.AllRole {
query = append(query, "u.role = :role")
}
// If there are search params presents, use search and ignore other options.
// Always combine role with search params, so len(query) > 1.
if len(query) > 1 {
return fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")), nil
}
if mq != "" {
query = append(query, mq)
}
if len(pm.IDs) != 0 {
query = append(query, fmt.Sprintf("id IN ('%s')", strings.Join(pm.IDs, "','")))
}
if pm.Status != users.AllStatus {
query = append(query, "u.status = :status")
}
var emq string
if len(query) > 0 {
emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
}
return emq, nil
}
func applyOrdering(emq string, pm users.Page) string {
switch pm.Order {
case "username", "first_name", "email", "last_name", "created_at", "updated_at":
emq = fmt.Sprintf("%s ORDER BY %s", emq, pm.Order)
if pm.Dir == api.AscDir || pm.Dir == api.DescDir {
emq = fmt.Sprintf("%s %s", emq, pm.Dir)
}
}
return emq
}
func stringToNullString(s string) sql.NullString {
if s == "" {
return sql.NullString{}
}
return sql.NullString{
String: s,
Valid: true,
}
}
func nullStringString(ns sql.NullString) string {
if ns.Valid {
return ns.String
}
return ""
}
+703
View File
@@ -0,0 +1,703 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package postgres_test
import (
"context"
"fmt"
"strings"
"testing"
"github.com/0x6flab/namegenerator"
"github.com/absmach/magistrala/internal/testsutil"
"github.com/absmach/magistrala/pkg/errors"
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
"github.com/absmach/magistrala/users"
cpostgres "github.com/absmach/magistrala/users/postgres"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const maxNameSize = 254
var (
invalidName = strings.Repeat("m", maxNameSize+10)
password = "$tr0ngPassw0rd"
namesgen = namegenerator.NewGenerator()
)
func TestUsersSave(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM users")
require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err))
})
repo := cpostgres.NewRepository(database)
uid := testsutil.GenerateUUID(t)
first_name := namesgen.Generate()
last_name := namesgen.Generate()
username := namesgen.Generate()
email := first_name + "@example.com"
cases := []struct {
desc string
user users.User
err error
}{
{
desc: "add new user successfully",
user: users.User{
ID: uid,
FirstName: first_name,
LastName: last_name,
Email: email,
Credentials: users.Credentials{
Username: username,
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
},
err: nil,
},
{
desc: "add user with duplicate user email",
user: users.User{
ID: testsutil.GenerateUUID(t),
FirstName: first_name,
LastName: last_name,
Email: email,
Credentials: users.Credentials{
Username: namesgen.Generate(),
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
},
err: repoerr.ErrConflict,
},
{
desc: "add user with duplicate user name",
user: users.User{
ID: testsutil.GenerateUUID(t),
FirstName: namesgen.Generate(),
LastName: last_name,
Email: namesgen.Generate() + "@example.com",
Credentials: users.Credentials{
Username: username,
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
},
err: repoerr.ErrConflict,
},
{
desc: "add user with invalid user id",
user: users.User{
ID: invalidName,
FirstName: namesgen.Generate(),
LastName: namesgen.Generate(),
Email: namesgen.Generate() + "@example.com",
Credentials: users.Credentials{
Username: username,
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
},
err: errors.ErrMalformedEntity,
},
{
desc: "add user with invalid user name",
user: users.User{
ID: testsutil.GenerateUUID(t),
FirstName: first_name,
LastName: last_name,
Email: namesgen.Generate() + "@example.com",
Credentials: users.Credentials{
Username: invalidName,
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
},
err: errors.ErrMalformedEntity,
},
{
desc: "add user with a missing username",
user: users.User{
ID: testsutil.GenerateUUID(t),
FirstName: first_name,
LastName: last_name,
Email: namesgen.Generate() + "@example.com",
Credentials: users.Credentials{
Secret: password,
},
Metadata: users.Metadata{},
},
err: nil,
},
{
desc: "add user with a missing user secret",
user: users.User{
ID: testsutil.GenerateUUID(t),
FirstName: namesgen.Generate(),
LastName: namesgen.Generate(),
Email: namesgen.Generate() + "@example.com",
Credentials: users.Credentials{
Username: namesgen.Generate(),
},
Metadata: users.Metadata{},
},
err: nil,
},
{
desc: "add a user with invalid metadata",
user: users.User{
ID: testsutil.GenerateUUID(t),
FirstName: namesgen.Generate(),
Email: namesgen.Generate() + "@example.com",
Credentials: users.Credentials{
Username: username,
Secret: password,
},
Metadata: map[string]interface{}{
"key": make(chan int),
},
},
err: errors.ErrMalformedEntity,
},
}
for _, tc := range cases {
rUser, err := repo.Save(context.Background(), tc.user)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
rUser.Credentials.Secret = tc.user.Credentials.Secret
assert.Equal(t, tc.user, rUser, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.user, rUser))
}
}
}
func TestIsPlatformAdmin(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM users")
require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err))
})
repo := cpostgres.NewRepository(database)
first_name := namesgen.Generate()
last_name := namesgen.Generate()
username := namesgen.Generate()
email := first_name + "@example.com"
cases := []struct {
desc string
user users.User
err error
}{
{
desc: "authorize check for super user",
user: users.User{
ID: testsutil.GenerateUUID(t),
FirstName: first_name,
LastName: last_name,
Email: email,
Credentials: users.Credentials{
Username: username,
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
Role: users.AdminRole,
},
err: nil,
},
{
desc: "unauthorize user",
user: users.User{
ID: testsutil.GenerateUUID(t),
FirstName: first_name,
LastName: last_name,
Email: namesgen.Generate() + "@example.com",
Credentials: users.Credentials{
Username: namesgen.Generate(),
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
Role: users.UserRole,
},
err: repoerr.ErrNotFound,
},
}
for _, tc := range cases {
_, err := repo.Save(context.Background(), tc.user)
require.Nil(t, err, fmt.Sprintf("%s: save user unexpected error: %s", tc.desc, err))
err = repo.CheckSuperAdmin(context.Background(), tc.user.ID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err))
}
}
func TestRetrieveByID(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM users")
require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err))
})
repo := cpostgres.NewRepository(database)
user := users.User{
ID: testsutil.GenerateUUID(t),
FirstName: namesgen.Generate(),
LastName: namesgen.Generate(),
Email: namesgen.Generate() + "@example.com",
Credentials: users.Credentials{
Username: namesgen.Generate(),
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
}
_, err := repo.Save(context.Background(), user)
require.Nil(t, err, fmt.Sprintf("failed to save users %s", user.ID))
cases := []struct {
desc string
userID string
err error
}{
{
desc: "retrieve existing user",
userID: user.ID,
err: nil,
},
{
desc: "retrieve non-existing user",
userID: invalidName,
err: repoerr.ErrNotFound,
},
{
desc: "retrieve with empty user id",
userID: "",
err: repoerr.ErrNotFound,
},
}
for _, tc := range cases {
_, err := repo.RetrieveByID(context.Background(), tc.userID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err))
}
}
func TestRetrieveAll(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM users")
require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err))
})
repo := cpostgres.NewRepository(database)
num := 200
var items, enabledUsers []users.User
for i := 0; i < num; i++ {
user := users.User{
ID: testsutil.GenerateUUID(t),
FirstName: namesgen.Generate(),
LastName: namesgen.Generate(),
Email: namesgen.Generate() + "@example.com",
Credentials: users.Credentials{
Username: namesgen.Generate(),
Secret: "",
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
Tags: []string{"tag1"},
}
if i%50 == 0 {
user.Metadata = map[string]interface{}{
"key": "value",
}
user.Role = users.AdminRole
user.Status = users.DisabledStatus
}
_, err := repo.Save(context.Background(), user)
require.Nil(t, err, fmt.Sprintf("failed to save user %s", user.ID))
items = append(items, user)
if user.Status == users.EnabledStatus {
enabledUsers = append(enabledUsers, user)
}
}
cases := []struct {
desc string
pageMeta users.Page
page users.UsersPage
err error
}{
{
desc: "retrieve first page of users",
pageMeta: users.Page{
Offset: 0,
Limit: 50,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 200,
Offset: 0,
Limit: 50,
},
Users: items[0:50],
},
err: nil,
},
{
desc: "retrieve second page of users",
pageMeta: users.Page{
Offset: 50,
Limit: 200,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 200,
Offset: 50,
Limit: 200,
},
Users: items[50:200],
},
err: nil,
},
{
desc: "retrieve users with limit",
pageMeta: users.Page{
Offset: 0,
Limit: 50,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: uint64(num),
Offset: 0,
Limit: 50,
},
Users: items[:50],
},
},
{
desc: "retrieve with offset out of range",
pageMeta: users.Page{
Offset: 1000,
Limit: 200,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 200,
Offset: 1000,
Limit: 200,
},
Users: []users.User{},
},
err: nil,
},
{
desc: "retrieve with limit out of range",
pageMeta: users.Page{
Offset: 0,
Limit: 1000,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 200,
Offset: 0,
Limit: 1000,
},
Users: items,
},
err: nil,
},
{
desc: "retrieve with empty page",
pageMeta: users.Page{},
page: users.UsersPage{
Page: users.Page{
Total: 196, // number of enabled users
Offset: 0,
Limit: 0,
},
Users: []users.User{},
},
err: nil,
},
{
desc: "retrieve with user id",
pageMeta: users.Page{
IDs: []string{items[0].ID},
Offset: 0,
Limit: 3,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 1,
Offset: 0,
Limit: 3,
},
Users: []users.User{items[0]},
},
err: nil,
},
{
desc: "retrieve with invalid user id",
pageMeta: users.Page{
IDs: []string{invalidName},
Offset: 0,
Limit: 3,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 0,
Offset: 0,
Limit: 3,
},
Users: []users.User{},
},
err: nil,
},
{
desc: "retrieve with first name",
pageMeta: users.Page{
FirstName: items[0].FirstName,
Offset: 0,
Limit: 3,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 1,
Offset: 0,
Limit: 3,
},
Users: []users.User{items[0]},
},
err: nil,
},
{
desc: "retrieve with username",
pageMeta: users.Page{
Username: items[0].Credentials.Username,
Offset: 0,
Limit: 3,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 1,
Offset: 0,
Limit: 3,
},
Users: []users.User{items[0]},
},
err: nil,
},
{
desc: "retrieve with enabled status",
pageMeta: users.Page{
Status: users.EnabledStatus,
Offset: 0,
Limit: 200,
Role: users.AllRole,
},
page: users.UsersPage{
Page: users.Page{
Total: 196,
Offset: 0,
Limit: 200,
},
Users: enabledUsers,
},
err: nil,
},
{
desc: "retrieve with disabled status",
pageMeta: users.Page{
Status: users.DisabledStatus,
Offset: 0,
Limit: 200,
Role: users.AllRole,
},
page: users.UsersPage{
Page: users.Page{
Total: 4,
Offset: 0,
Limit: 200,
},
Users: []users.User{items[0], items[50], items[100], items[150]},
},
},
{
desc: "retrieve with all status",
pageMeta: users.Page{
Status: users.AllStatus,
Offset: 0,
Limit: 200,
Role: users.AllRole,
},
page: users.UsersPage{
Page: users.Page{
Total: 200,
Offset: 0,
Limit: 200,
},
Users: items,
},
},
{
desc: "retrieve by tags",
pageMeta: users.Page{
Tag: "tag1",
Offset: 0,
Limit: 200,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 200,
Offset: 0,
Limit: 200,
},
Users: items,
},
err: nil,
},
{
desc: "retrieve with invalid first name",
pageMeta: users.Page{
FirstName: invalidName,
Offset: 0,
Limit: 3,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 0,
Offset: 0,
Limit: 3,
},
Users: []users.User{},
},
},
{
desc: "retrieve with metadata",
pageMeta: users.Page{
Metadata: map[string]interface{}{
"key": "value",
},
Offset: 0,
Limit: 200,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 4,
Offset: 0,
Limit: 200,
},
Users: []users.User{items[0], items[50], items[100], items[150]},
},
err: nil,
},
{
desc: "retrieve with invalid metadata",
pageMeta: users.Page{
Metadata: map[string]interface{}{
"key": "value1",
},
Offset: 0,
Limit: 200,
Role: users.AllRole,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 0,
Offset: 0,
Limit: 200,
},
Users: []users.User{},
},
err: nil,
},
{
desc: "retrieve with role",
pageMeta: users.Page{
Role: users.AdminRole,
Offset: 0,
Limit: 200,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 4,
Offset: 0,
Limit: 200,
},
Users: []users.User{items[0], items[50], items[100], items[150]},
},
err: nil,
},
{
desc: "retrieve with invalid role",
pageMeta: users.Page{
Role: users.AdminRole + 2,
Offset: 0,
Limit: 200,
Status: users.AllStatus,
},
page: users.UsersPage{
Page: users.Page{
Total: 0,
Offset: 0,
Limit: 200,
},
Users: []users.User{},
},
err: nil,
},
}
for _, tc := range cases {
page, err := repo.RetrieveAll(context.Background(), tc.pageMeta)
assert.Equal(t, tc.page.Total, page.Total, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Total, page.Total))
assert.Equal(t, tc.page.Offset, page.Offset, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Offset, page.Offset))
assert.Equal(t, tc.page.Limit, page.Limit, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Limit, page.Limit))
assert.Equal(t, tc.page.Page, page.Page, fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.page, page))
assert.ElementsMatch(t, tc.page.Users, page.Users, fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.page.Users, page.Users))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package users
import (
"encoding/json"
"strings"
"github.com/absmach/magistrala/pkg/apiutil"
)
// Role represents User role.
type Role uint8
// Possible User role values.
const (
UserRole Role = iota
AdminRole
// AllRole is used for querying purposes to list users irrespective
// of their role - both admin and user. It is never stored in the
// database as the actual user role and should always be the largest
// value in this enumeration.
AllRole
)
// String representation of the possible role values.
const (
Admin = "admin"
user = "user"
)
// String converts user role to string literal.
func (cs Role) String() string {
switch cs {
case AdminRole:
return Admin
case UserRole:
return user
case AllRole:
return All
default:
return Unknown
}
}
// ToRole converts string value to a valid User role.
func ToRole(status string) (Role, error) {
switch status {
case "", user:
return UserRole, nil
case Admin:
return AdminRole, nil
case All:
return AllRole, nil
default:
return Role(0), apiutil.ErrInvalidRole
}
}
func (r Role) MarshalJSON() ([]byte, error) {
return json.Marshal(r.String())
}
func (r *Role) UnmarshalJSON(data []byte) error {
str := strings.Trim(string(data), "\"")
val, err := ToRole(str)
*r = val
return err
}
+248 -185
View File
@@ -5,6 +5,7 @@ package users
import (
"context"
"net/mail"
"time"
"github.com/absmach/magistrala"
@@ -15,7 +16,6 @@ import (
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
"github.com/absmach/magistrala/pkg/policies"
"github.com/absmach/magistrala/users/postgres"
"golang.org/x/sync/errgroup"
)
@@ -28,7 +28,7 @@ var (
type service struct {
token magistrala.TokenServiceClient
clients postgres.Repository
users Repository
idProvider magistrala.IDProvider
policies policies.Service
hasher Hasher
@@ -36,10 +36,10 @@ type service struct {
}
// NewService returns a new Users service implementation.
func NewService(token magistrala.TokenServiceClient, crepo postgres.Repository, policyService policies.Service, emailer Emailer, hasher Hasher, idp magistrala.IDProvider) Service {
func NewService(token magistrala.TokenServiceClient, urepo Repository, policyService policies.Service, emailer Emailer, hasher Hasher, idp magistrala.IDProvider) Service {
return service{
token: token,
clients: crepo,
users: urepo,
policies: policyService,
hasher: hasher,
email: emailer,
@@ -47,57 +47,66 @@ func NewService(token magistrala.TokenServiceClient, crepo postgres.Repository,
}
}
func (svc service) RegisterClient(ctx context.Context, session authn.Session, cli mgclients.Client, selfRegister bool) (rc mgclients.Client, err error) {
func (svc service) Register(ctx context.Context, session authn.Session, u User, selfRegister bool) (uc User, err error) {
if !selfRegister {
if err := svc.checkSuperAdmin(ctx, session); err != nil {
return mgclients.Client{}, err
return User{}, err
}
}
clientID, err := svc.idProvider.ID()
userID, err := svc.idProvider.ID()
if err != nil {
return mgclients.Client{}, err
return User{}, err
}
if cli.Credentials.Secret != "" {
hash, err := svc.hasher.Hash(cli.Credentials.Secret)
if u.Credentials.Secret != "" {
hash, err := svc.hasher.Hash(u.Credentials.Secret)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrMalformedEntity, err)
return User{}, errors.Wrap(svcerr.ErrMalformedEntity, err)
}
cli.Credentials.Secret = hash
u.Credentials.Secret = hash
}
if cli.Status != mgclients.DisabledStatus && cli.Status != mgclients.EnabledStatus {
return mgclients.Client{}, errors.Wrap(svcerr.ErrMalformedEntity, svcerr.ErrInvalidStatus)
if u.Status != DisabledStatus && u.Status != EnabledStatus {
return User{}, errors.Wrap(svcerr.ErrMalformedEntity, svcerr.ErrInvalidStatus)
}
if cli.Role != mgclients.UserRole && cli.Role != mgclients.AdminRole {
return mgclients.Client{}, errors.Wrap(svcerr.ErrMalformedEntity, svcerr.ErrInvalidRole)
if u.Role != UserRole && u.Role != AdminRole {
return User{}, errors.Wrap(svcerr.ErrMalformedEntity, svcerr.ErrInvalidRole)
}
cli.ID = clientID
cli.CreatedAt = time.Now()
u.ID = userID
u.CreatedAt = time.Now()
if err := svc.addClientPolicy(ctx, cli.ID, cli.Role); err != nil {
return mgclients.Client{}, err
if err := svc.addUserPolicy(ctx, u.ID, u.Role); err != nil {
return User{}, err
}
defer func() {
if err != nil {
if errRollback := svc.addClientPolicyRollback(ctx, cli.ID, cli.Role); errRollback != nil {
if errRollback := svc.addUserPolicyRollback(ctx, u.ID, u.Role); errRollback != nil {
err = errors.Wrap(errors.Wrap(errors.ErrRollbackTx, errRollback), err)
}
}
}()
client, err := svc.clients.Save(ctx, cli)
user, err := svc.users.Save(ctx, u)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrCreateEntity, err)
return User{}, errors.Wrap(svcerr.ErrCreateEntity, err)
}
return client, nil
return user, nil
}
func (svc service) IssueToken(ctx context.Context, identity, secret string) (*magistrala.Token, error) {
dbUser, err := svc.clients.RetrieveByIdentity(ctx, identity)
var dbUser User
var err error
if _, parseErr := mail.ParseAddress(identity); parseErr != nil {
dbUser, err = svc.users.RetrieveByUsername(ctx, identity)
} else {
dbUser, err = svc.users.RetrieveByEmail(ctx, identity)
}
if err != nil {
return &magistrala.Token{}, errors.Wrap(svcerr.ErrAuthentication, err)
}
if err := svc.hasher.Compare(secret, dbUser.Credentials.Secret); err != nil {
return &magistrala.Token{}, errors.Wrap(svcerr.ErrLogin, err)
}
@@ -107,150 +116,178 @@ func (svc service) IssueToken(ctx context.Context, identity, secret string) (*ma
return &magistrala.Token{}, errors.Wrap(errIssueToken, err)
}
return token, err
return token, nil
}
func (svc service) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error) {
dbUser, err := svc.clients.RetrieveByID(ctx, session.UserID)
dbUser, err := svc.users.RetrieveByID(ctx, session.UserID)
if err != nil {
return &magistrala.Token{}, errors.Wrap(svcerr.ErrAuthentication, err)
}
if dbUser.Status == mgclients.DisabledStatus {
if dbUser.Status == DisabledStatus {
return &magistrala.Token{}, errors.Wrap(svcerr.ErrAuthentication, errLoginDisableUser)
}
return svc.token.Refresh(ctx, &magistrala.RefreshReq{RefreshToken: refreshToken})
}
func (svc service) ViewClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
client, err := svc.clients.RetrieveByID(ctx, id)
func (svc service) View(ctx context.Context, session authn.Session, id string) (User, error) {
user, err := svc.users.RetrieveByID(ctx, id)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrViewEntity, err)
return User{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
if session.UserID != id {
if err := svc.checkSuperAdmin(ctx, session); err != nil {
return mgclients.Client{Name: client.Name, ID: client.ID}, nil
return User{
FirstName: user.FirstName,
LastName: user.LastName,
ID: user.ID,
Credentials: Credentials{Username: user.Credentials.Username},
}, nil
}
}
client.Credentials.Secret = ""
user.Credentials.Secret = ""
return client, nil
return user, nil
}
func (svc service) ViewProfile(ctx context.Context, session authn.Session) (mgclients.Client, error) {
client, err := svc.clients.RetrieveByID(ctx, session.UserID)
func (svc service) ViewProfile(ctx context.Context, session authn.Session) (User, error) {
user, err := svc.users.RetrieveByID(ctx, session.UserID)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrViewEntity, err)
return User{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
client.Credentials.Secret = ""
user.Credentials.Secret = ""
return client, nil
return user, nil
}
func (svc service) ListClients(ctx context.Context, session authn.Session, pm mgclients.Page) (mgclients.ClientsPage, error) {
func (svc service) ListUsers(ctx context.Context, session authn.Session, pm Page) (UsersPage, error) {
if err := svc.checkSuperAdmin(ctx, session); err != nil {
return mgclients.ClientsPage{}, err
return UsersPage{}, err
}
pm.Role = mgclients.AllRole
pg, err := svc.clients.RetrieveAll(ctx, pm)
pm.Role = AllRole
pg, err := svc.users.RetrieveAll(ctx, pm)
if err != nil {
return mgclients.ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
return UsersPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
return pg, err
}
func (svc service) SearchUsers(ctx context.Context, pm mgclients.Page) (mgclients.ClientsPage, error) {
page := mgclients.Page{
Offset: pm.Offset,
Limit: pm.Limit,
Name: pm.Name,
Id: pm.Id,
Role: mgclients.UserRole,
func (svc service) SearchUsers(ctx context.Context, pm Page) (UsersPage, error) {
page := Page{
Offset: pm.Offset,
Limit: pm.Limit,
FirstName: pm.FirstName,
LastName: pm.LastName,
Username: pm.Username,
Id: pm.Id,
Role: UserRole,
}
cp, err := svc.clients.SearchClients(ctx, page)
cp, err := svc.users.SearchUsers(ctx, page)
if err != nil {
return mgclients.ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
return UsersPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
return cp, nil
}
func (svc service) UpdateClient(ctx context.Context, session authn.Session, cli mgclients.Client) (mgclients.Client, error) {
if session.UserID != cli.ID {
func (svc service) Update(ctx context.Context, session authn.Session, usr User) (User, error) {
if session.UserID != usr.ID {
if err := svc.checkSuperAdmin(ctx, session); err != nil {
return mgclients.Client{}, err
return User{}, err
}
}
client := mgclients.Client{
ID: cli.ID,
Name: cli.Name,
Metadata: cli.Metadata,
user := User{
ID: usr.ID,
FirstName: usr.FirstName,
LastName: usr.LastName,
Metadata: usr.Metadata,
UpdatedAt: time.Now(),
UpdatedBy: session.UserID,
}
client, err := svc.clients.Update(ctx, client)
user, err := svc.users.Update(ctx, user)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return client, nil
return user, nil
}
func (svc service) UpdateClientTags(ctx context.Context, session authn.Session, cli mgclients.Client) (mgclients.Client, error) {
if session.UserID != cli.ID {
func (svc service) UpdateTags(ctx context.Context, session authn.Session, usr User) (User, error) {
if session.UserID != usr.ID {
if err := svc.checkSuperAdmin(ctx, session); err != nil {
return mgclients.Client{}, err
return User{}, err
}
}
client := mgclients.Client{
ID: cli.ID,
Tags: cli.Tags,
user := User{
ID: usr.ID,
Tags: usr.Tags,
UpdatedAt: time.Now(),
UpdatedBy: session.UserID,
}
client, err := svc.clients.UpdateTags(ctx, client)
user, err := svc.users.Update(ctx, user)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return client, nil
return user, nil
}
func (svc service) UpdateClientIdentity(ctx context.Context, session authn.Session, clientID, identity string) (mgclients.Client, error) {
if session.UserID != clientID {
func (svc service) UpdateProfilePicture(ctx context.Context, session authn.Session, usr User) (User, error) {
if session.UserID != usr.ID {
if err := svc.checkSuperAdmin(ctx, session); err != nil {
return mgclients.Client{}, err
return User{}, err
}
}
cli := mgclients.Client{
ID: clientID,
Credentials: mgclients.Credentials{
Identity: identity,
},
user := User{
ID: usr.ID,
ProfilePicture: usr.ProfilePicture,
UpdatedAt: time.Now(),
UpdatedBy: session.UserID,
}
user, err := svc.users.Update(ctx, user)
if err != nil {
return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return user, nil
}
func (svc service) UpdateEmail(ctx context.Context, session authn.Session, userID, email string) (User, error) {
if session.UserID != userID {
if err := svc.checkSuperAdmin(ctx, session); err != nil {
return User{}, err
}
}
user := User{
ID: userID,
Email: email,
UpdatedAt: time.Now(),
UpdatedBy: session.UserID,
}
cli, err := svc.clients.UpdateIdentity(ctx, cli)
user, err := svc.users.Update(ctx, user)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return cli, nil
return user, nil
}
func (svc service) GenerateResetToken(ctx context.Context, email, host string) error {
client, err := svc.clients.RetrieveByIdentity(ctx, email)
user, err := svc.users.RetrieveByEmail(ctx, email)
if err != nil {
return errors.Wrap(svcerr.ErrViewEntity, err)
}
issueReq := &magistrala.IssueReq{
UserId: client.ID,
UserId: user.ID,
Type: uint32(mgauth.RecoveryKey),
}
token, err := svc.token.Issue(ctx, issueReq)
@@ -258,11 +295,11 @@ func (svc service) GenerateResetToken(ctx context.Context, email, host string) e
return errors.Wrap(errRecoveryToken, err)
}
return svc.SendPasswordReset(ctx, host, email, client.Name, token.AccessToken)
return svc.SendPasswordReset(ctx, host, email, user.Credentials.Username, token.AccessToken)
}
func (svc service) ResetSecret(ctx context.Context, session authn.Session, secret string) error {
c, err := svc.clients.RetrieveByID(ctx, session.UserID)
u, err := svc.users.RetrieveByID(ctx, session.UserID)
if err != nil {
return errors.Wrap(svcerr.ErrViewEntity, err)
}
@@ -271,43 +308,65 @@ func (svc service) ResetSecret(ctx context.Context, session authn.Session, secre
if err != nil {
return errors.Wrap(svcerr.ErrMalformedEntity, err)
}
c = mgclients.Client{
ID: c.ID,
Credentials: mgclients.Credentials{
Identity: c.Credentials.Identity,
Secret: secret,
u = User{
ID: u.ID,
Email: u.Email,
Credentials: Credentials{
Secret: secret,
},
UpdatedAt: time.Now(),
UpdatedBy: session.UserID,
}
if _, err := svc.clients.UpdateSecret(ctx, c); err != nil {
if _, err := svc.users.UpdateSecret(ctx, u); err != nil {
return errors.Wrap(svcerr.ErrAuthorization, err)
}
return nil
}
func (svc service) UpdateClientSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (mgclients.Client, error) {
dbClient, err := svc.clients.RetrieveByID(ctx, session.UserID)
func (svc service) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (User, error) {
dbUser, err := svc.users.RetrieveByID(ctx, session.UserID)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrViewEntity, err)
return User{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
if _, err := svc.IssueToken(ctx, dbClient.Credentials.Identity, oldSecret); err != nil {
return mgclients.Client{}, err
if _, err := svc.IssueToken(ctx, dbUser.Credentials.Username, oldSecret); err != nil {
return User{}, err
}
newSecret, err = svc.hasher.Hash(newSecret)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrMalformedEntity, err)
return User{}, errors.Wrap(svcerr.ErrMalformedEntity, err)
}
dbClient.Credentials.Secret = newSecret
dbClient.UpdatedAt = time.Now()
dbClient.UpdatedBy = session.UserID
dbUser.Credentials.Secret = newSecret
dbUser.UpdatedAt = time.Now()
dbUser.UpdatedBy = session.UserID
dbClient, err = svc.clients.UpdateSecret(ctx, dbClient)
dbUser, err = svc.users.UpdateSecret(ctx, dbUser)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return dbClient, nil
return dbUser, nil
}
func (svc service) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (User, error) {
if session.UserID != id {
if err := svc.checkSuperAdmin(ctx, session); err != nil {
return User{}, err
}
}
usr := User{
ID: id,
Credentials: Credentials{
Username: username,
},
UpdatedAt: time.Now(),
UpdatedBy: session.UserID,
}
updatedUser, err := svc.users.UpdateUsername(ctx, usr)
if err != nil {
return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return updatedUser, nil
}
func (svc service) SendPasswordReset(_ context.Context, host, email, user, token string) error {
@@ -315,97 +374,97 @@ func (svc service) SendPasswordReset(_ context.Context, host, email, user, token
return svc.email.SendPasswordReset(to, host, user, token)
}
func (svc service) UpdateClientRole(ctx context.Context, session authn.Session, cli mgclients.Client) (mgclients.Client, error) {
func (svc service) UpdateRole(ctx context.Context, session authn.Session, usr User) (User, error) {
if err := svc.checkSuperAdmin(ctx, session); err != nil {
return mgclients.Client{}, err
return User{}, err
}
client := mgclients.Client{
ID: cli.ID,
Role: cli.Role,
user := User{
ID: usr.ID,
Role: usr.Role,
UpdatedAt: time.Now(),
UpdatedBy: session.UserID,
}
if err := svc.updateClientPolicy(ctx, cli.ID, cli.Role); err != nil {
return mgclients.Client{}, err
if err := svc.updateUserPolicy(ctx, usr.ID, usr.Role); err != nil {
return User{}, err
}
client, err := svc.clients.UpdateRole(ctx, client)
u, err := svc.users.Update(ctx, user)
if err != nil {
// If failed to update role in DB, then revert back to platform admin policies in spicedb
if errRollback := svc.updateClientPolicy(ctx, cli.ID, mgclients.UserRole); errRollback != nil {
return mgclients.Client{}, errors.Wrap(errRollback, err)
if errRollback := svc.updateUserPolicy(ctx, usr.ID, UserRole); errRollback != nil {
return User{}, errors.Wrap(errRollback, err)
}
return mgclients.Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return client, nil
return u, nil
}
func (svc service) EnableClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
client := mgclients.Client{
func (svc service) Enable(ctx context.Context, session authn.Session, id string) (User, error) {
u := User{
ID: id,
UpdatedAt: time.Now(),
Status: mgclients.EnabledStatus,
Status: EnabledStatus,
}
client, err := svc.changeClientStatus(ctx, session, client)
user, err := svc.changeUserStatus(ctx, session, u)
if err != nil {
return mgclients.Client{}, errors.Wrap(mgclients.ErrEnableClient, err)
return User{}, errors.Wrap(mgclients.ErrEnableClient, err)
}
return client, nil
return user, nil
}
func (svc service) DisableClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
client := mgclients.Client{
func (svc service) Disable(ctx context.Context, session authn.Session, id string) (User, error) {
user := User{
ID: id,
UpdatedAt: time.Now(),
Status: mgclients.DisabledStatus,
Status: DisabledStatus,
}
client, err := svc.changeClientStatus(ctx, session, client)
user, err := svc.changeUserStatus(ctx, session, user)
if err != nil {
return mgclients.Client{}, err
return User{}, err
}
return client, nil
return user, nil
}
func (svc service) changeClientStatus(ctx context.Context, session authn.Session, client mgclients.Client) (mgclients.Client, error) {
if session.UserID != client.ID {
func (svc service) changeUserStatus(ctx context.Context, session authn.Session, user User) (User, error) {
if session.UserID != user.ID {
if err := svc.checkSuperAdmin(ctx, session); err != nil {
return mgclients.Client{}, err
return User{}, err
}
}
dbClient, err := svc.clients.RetrieveByID(ctx, client.ID)
dbu, err := svc.users.RetrieveByID(ctx, user.ID)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrViewEntity, err)
return User{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
if dbClient.Status == client.Status {
return mgclients.Client{}, errors.ErrStatusAlreadyAssigned
if dbu.Status == user.Status {
return User{}, errors.ErrStatusAlreadyAssigned
}
client.UpdatedBy = session.UserID
user.UpdatedBy = session.UserID
client, err = svc.clients.ChangeStatus(ctx, client)
user, err = svc.users.ChangeStatus(ctx, user)
if err != nil {
return mgclients.Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return client, nil
return user, nil
}
func (svc service) DeleteClient(ctx context.Context, session authn.Session, id string) error {
client := mgclients.Client{
func (svc service) Delete(ctx context.Context, session authn.Session, id string) error {
user := User{
ID: id,
UpdatedAt: time.Now(),
Status: mgclients.DeletedStatus,
Status: DeletedStatus,
}
if _, err := svc.changeClientStatus(ctx, session, client); err != nil {
if _, err := svc.changeUserStatus(ctx, session, user); err != nil {
return err
}
return nil
}
func (svc service) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm mgclients.Page) (mgclients.MembersPage, error) {
func (svc service) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm Page) (MembersPage, error) {
var objectType string
switch objectKind {
case policies.ThingsKind:
@@ -425,11 +484,11 @@ func (svc service) ListMembers(ctx context.Context, session authn.Session, objec
ObjectType: objectType,
})
if err != nil {
return mgclients.MembersPage{}, errors.Wrap(svcerr.ErrNotFound, err)
return MembersPage{}, errors.Wrap(svcerr.ErrNotFound, err)
}
if len(duids.Policies) == 0 {
return mgclients.MembersPage{
Page: mgclients.Page{Total: 0, Offset: pm.Offset, Limit: pm.Limit},
return MembersPage{
Page: Page{Total: 0, Offset: pm.Offset, Limit: pm.Limit},
}, nil
}
@@ -441,50 +500,54 @@ func (svc service) ListMembers(ctx context.Context, session authn.Session, objec
}
pm.IDs = userIDs
cp, err := svc.clients.RetrieveAll(ctx, pm)
up, err := svc.users.RetrieveAll(ctx, pm)
if err != nil {
return mgclients.MembersPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
return MembersPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
for i, c := range cp.Clients {
cp.Clients[i] = mgclients.Client{
ID: c.ID,
Name: c.Name,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
Status: c.Status,
for i, u := range up.Users {
up.Users[i] = User{
ID: u.ID,
FirstName: u.FirstName,
LastName: u.LastName,
Credentials: Credentials{
Username: u.Credentials.Username,
},
CreatedAt: u.CreatedAt,
UpdatedAt: u.UpdatedAt,
Status: u.Status,
}
}
if pm.ListPerms && len(cp.Clients) > 0 {
if pm.ListPerms && len(up.Users) > 0 {
g, ctx := errgroup.WithContext(ctx)
for i := range cp.Clients {
for i := range up.Users {
// Copying loop variable "i" to avoid "loop variable captured by func literal"
iter := i
g.Go(func() error {
return svc.retrieveObjectUsersPermissions(ctx, session.DomainID, objectType, objectID, &cp.Clients[iter])
return svc.retrieveObjectUsersPermissions(ctx, session.DomainID, objectType, objectID, &up.Users[iter])
})
}
if err := g.Wait(); err != nil {
return mgclients.MembersPage{}, err
return MembersPage{}, err
}
}
return mgclients.MembersPage{
Page: cp.Page,
Members: cp.Clients,
return MembersPage{
Page: up.Page,
Members: up.Users,
}, nil
}
func (svc service) retrieveObjectUsersPermissions(ctx context.Context, domainID, objectType, objectID string, client *mgclients.Client) error {
userID := mgauth.EncodeDomainUserID(domainID, client.ID)
func (svc service) retrieveObjectUsersPermissions(ctx context.Context, domainID, objectType, objectID string, user *User) error {
userID := mgauth.EncodeDomainUserID(domainID, user.ID)
permissions, err := svc.listObjectUserPermission(ctx, userID, objectType, objectID)
if err != nil {
return errors.Wrap(svcerr.ErrAuthorization, err)
}
client.Permissions = permissions
user.Permissions = permissions
return nil
}
@@ -503,7 +566,7 @@ func (svc service) listObjectUserPermission(ctx context.Context, userID, objectT
func (svc *service) checkSuperAdmin(ctx context.Context, session authn.Session) error {
if !session.SuperAdmin {
if err := svc.clients.CheckSuperAdmin(ctx, session.UserID); err != nil {
if err := svc.users.CheckSuperAdmin(ctx, session.UserID); err != nil {
return errors.Wrap(svcerr.ErrAuthorization, err)
}
}
@@ -511,35 +574,35 @@ func (svc *service) checkSuperAdmin(ctx context.Context, session authn.Session)
return nil
}
func (svc service) OAuthCallback(ctx context.Context, client mgclients.Client) (mgclients.Client, error) {
rclient, err := svc.clients.RetrieveByIdentity(ctx, client.Credentials.Identity)
func (svc service) OAuthCallback(ctx context.Context, user User) (User, error) {
ruser, err := svc.users.RetrieveByEmail(ctx, user.Email)
if err != nil {
switch errors.Contains(err, repoerr.ErrNotFound) {
case true:
rclient, err = svc.RegisterClient(ctx, authn.Session{}, client, true)
ruser, err = svc.Register(ctx, authn.Session{}, user, true)
if err != nil {
return mgclients.Client{}, err
return User{}, err
}
default:
return mgclients.Client{}, err
return User{}, err
}
}
return mgclients.Client{
ID: rclient.ID,
Role: rclient.Role,
return User{
ID: ruser.ID,
Role: ruser.Role,
}, nil
}
func (svc service) OAuthAddClientPolicy(ctx context.Context, client mgclients.Client) error {
return svc.addClientPolicy(ctx, client.ID, client.Role)
func (svc service) OAuthAddUserPolicy(ctx context.Context, user User) error {
return svc.addUserPolicy(ctx, user.ID, user.Role)
}
func (svc service) Identify(ctx context.Context, session authn.Session) (string, error) {
return session.UserID, nil
}
func (svc service) addClientPolicy(ctx context.Context, userID string, role mgclients.Role) error {
func (svc service) addUserPolicy(ctx context.Context, userID string, role Role) error {
policyList := []policies.Policy{}
policyList = append(policyList, policies.Policy{
@@ -550,7 +613,7 @@ func (svc service) addClientPolicy(ctx context.Context, userID string, role mgcl
Object: policies.MagistralaObject,
})
if role == mgclients.AdminRole {
if role == AdminRole {
policyList = append(policyList, policies.Policy{
SubjectType: policies.UserType,
Subject: userID,
@@ -567,7 +630,7 @@ func (svc service) addClientPolicy(ctx context.Context, userID string, role mgcl
return nil
}
func (svc service) addClientPolicyRollback(ctx context.Context, userID string, role mgclients.Role) error {
func (svc service) addUserPolicyRollback(ctx context.Context, userID string, role Role) error {
policyList := []policies.Policy{}
policyList = append(policyList, policies.Policy{
@@ -578,7 +641,7 @@ func (svc service) addClientPolicyRollback(ctx context.Context, userID string, r
Object: policies.MagistralaObject,
})
if role == mgclients.AdminRole {
if role == AdminRole {
policyList = append(policyList, policies.Policy{
SubjectType: policies.UserType,
Subject: userID,
@@ -595,9 +658,9 @@ func (svc service) addClientPolicyRollback(ctx context.Context, userID string, r
return nil
}
func (svc service) updateClientPolicy(ctx context.Context, userID string, role mgclients.Role) error {
func (svc service) updateUserPolicy(ctx context.Context, userID string, role Role) error {
switch role {
case mgclients.AdminRole:
case AdminRole:
err := svc.policies.AddPolicy(ctx, policies.Policy{
SubjectType: policies.UserType,
Subject: userID,
@@ -610,7 +673,7 @@ func (svc service) updateClientPolicy(ctx context.Context, userID string, role m
}
return nil
case mgclients.UserRole:
case UserRole:
fallthrough
default:
err := svc.policies.DeletePolicyFilter(ctx, policies.Policy{
+680 -690
View File
File diff suppressed because it is too large Load Diff
+83
View File
@@ -0,0 +1,83 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package users
import (
"encoding/json"
"strings"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
)
// Status represents User status.
type Status uint8
// Possible User status values.
const (
// EnabledStatus represents enabled User.
EnabledStatus Status = iota
// DisabledStatus represents disabled User.
DisabledStatus
// DeletedStatus represents a user that will be deleted.
DeletedStatus
// AllStatus is used for querying purposes to list users irrespective
// of their status - both enabled and disabled. It is never stored in the
// database as the actual User status and should always be the largest
// value in this enumeration.
AllStatus
)
// String representation of the possible status values.
const (
Disabled = "disabled"
Enabled = "enabled"
Deleted = "deleted"
All = "all"
Unknown = "unknown"
)
// String converts user/group status to string literal.
func (s Status) String() string {
switch s {
case DisabledStatus:
return Disabled
case EnabledStatus:
return Enabled
case DeletedStatus:
return Deleted
case AllStatus:
return All
default:
return Unknown
}
}
// ToStatus converts string value to a valid User/Group status.
func ToStatus(status string) (Status, error) {
switch status {
case "", Enabled:
return EnabledStatus, nil
case Disabled:
return DisabledStatus, nil
case Deleted:
return DeletedStatus, nil
case All:
return AllStatus, nil
}
return Status(0), svcerr.ErrInvalidStatus
}
// Custom Marshaller for Uesr/Groups.
func (s Status) MarshalJSON() ([]byte, error) {
return json.Marshal(s.String())
}
// Custom Unmarshaler for User/Groups.
func (s *Status) UnmarshalJSON(data []byte) error {
str := strings.Trim(string(data), "\"")
val, err := ToStatus(str)
*s = val
return err
}
+92 -73
View File
@@ -8,8 +8,7 @@ import (
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/pkg/authn"
mgclients "github.com/absmach/magistrala/pkg/clients"
"github.com/absmach/magistrala/users"
users "github.com/absmach/magistrala/users"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
@@ -26,23 +25,23 @@ func New(svc users.Service, tracer trace.Tracer) users.Service {
return &tracingMiddleware{tracer, svc}
}
// RegisterClient traces the "RegisterClient" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) RegisterClient(ctx context.Context, session authn.Session, client mgclients.Client, selfRegister bool) (mgclients.Client, error) {
ctx, span := tm.tracer.Start(ctx, "svc_register_client", trace.WithAttributes(attribute.String("identity", client.Credentials.Identity)))
// Register traces the "Register" operation of the wrapped users.Service.
func (tm *tracingMiddleware) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_register_user", trace.WithAttributes(attribute.String("email", user.Email)))
defer span.End()
return tm.svc.RegisterClient(ctx, session, client, selfRegister)
return tm.svc.Register(ctx, session, user, selfRegister)
}
// IssueToken traces the "IssueToken" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) IssueToken(ctx context.Context, identity, secret string) (*magistrala.Token, error) {
ctx, span := tm.tracer.Start(ctx, "svc_issue_token", trace.WithAttributes(attribute.String("identity", identity)))
// IssueToken traces the "IssueToken" operation of the wrapped users.Service.
func (tm *tracingMiddleware) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) {
ctx, span := tm.tracer.Start(ctx, "svc_issue_token", trace.WithAttributes(attribute.String("username", username)))
defer span.End()
return tm.svc.IssueToken(ctx, identity, secret)
return tm.svc.IssueToken(ctx, username, secret)
}
// RefreshToken traces the "RefreshToken" operation of the wrapped clients.Service.
// RefreshToken traces the "RefreshToken" operation of the wrapped users.Service.
func (tm *tracingMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error) {
ctx, span := tm.tracer.Start(ctx, "svc_refresh_token", trace.WithAttributes(attribute.String("refresh_token", refreshToken)))
defer span.End()
@@ -50,17 +49,17 @@ func (tm *tracingMiddleware) RefreshToken(ctx context.Context, session authn.Ses
return tm.svc.RefreshToken(ctx, session, refreshToken)
}
// ViewClient traces the "ViewClient" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) ViewClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
ctx, span := tm.tracer.Start(ctx, "svc_view_client", trace.WithAttributes(attribute.String("id", id)))
// View traces the "View" operation of the wrapped users.Service.
func (tm *tracingMiddleware) View(ctx context.Context, session authn.Session, id string) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_view_user", trace.WithAttributes(attribute.String("id", id)))
defer span.End()
return tm.svc.ViewClient(ctx, session, id)
return tm.svc.View(ctx, session, id)
}
// ListClients traces the "ListClients" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) ListClients(ctx context.Context, session authn.Session, pm mgclients.Page) (mgclients.ClientsPage, error) {
ctx, span := tm.tracer.Start(ctx, "svc_list_clients", trace.WithAttributes(
// ListUsers traces the "ListUsers" operation of the wrapped users.Service.
func (tm *tracingMiddleware) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (users.UsersPage, error) {
ctx, span := tm.tracer.Start(ctx, "svc_list_users", trace.WithAttributes(
attribute.Int64("offset", int64(pm.Offset)),
attribute.Int64("limit", int64(pm.Limit)),
attribute.String("direction", pm.Dir),
@@ -69,12 +68,12 @@ func (tm *tracingMiddleware) ListClients(ctx context.Context, session authn.Sess
defer span.End()
return tm.svc.ListClients(ctx, session, pm)
return tm.svc.ListUsers(ctx, session, pm)
}
// SearchUsers traces the "SearchUsers" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) SearchUsers(ctx context.Context, pm mgclients.Page) (mgclients.ClientsPage, error) {
ctx, span := tm.tracer.Start(ctx, "svc_search_clients", trace.WithAttributes(
// SearchUsers traces the "SearchUsers" operation of the wrapped users.Service.
func (tm *tracingMiddleware) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) {
ctx, span := tm.tracer.Start(ctx, "svc_search_users", trace.WithAttributes(
attribute.Int64("offset", int64(pm.Offset)),
attribute.Int64("limit", int64(pm.Limit)),
attribute.String("direction", pm.Dir),
@@ -85,48 +84,68 @@ func (tm *tracingMiddleware) SearchUsers(ctx context.Context, pm mgclients.Page)
return tm.svc.SearchUsers(ctx, pm)
}
// UpdateClient traces the "UpdateClient" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) UpdateClient(ctx context.Context, session authn.Session, cli mgclients.Client) (mgclients.Client, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_client_name_and_metadata", trace.WithAttributes(
// Update traces the "Update" operation of the wrapped users.Service.
func (tm *tracingMiddleware) Update(ctx context.Context, session authn.Session, cli users.User) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_user", trace.WithAttributes(
attribute.String("id", cli.ID),
attribute.String("name", cli.Name),
attribute.String("first_name", cli.FirstName),
attribute.String("last_name", cli.LastName),
))
defer span.End()
return tm.svc.UpdateClient(ctx, session, cli)
return tm.svc.Update(ctx, session, cli)
}
// UpdateClientTags traces the "UpdateClientTags" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) UpdateClientTags(ctx context.Context, session authn.Session, cli mgclients.Client) (mgclients.Client, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_client_tags", trace.WithAttributes(
// UpdateTags traces the "UpdateTags" operation of the wrapped users.Service.
func (tm *tracingMiddleware) UpdateTags(ctx context.Context, session authn.Session, cli users.User) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_user_tags", trace.WithAttributes(
attribute.String("id", cli.ID),
attribute.StringSlice("tags", cli.Tags),
))
defer span.End()
return tm.svc.UpdateClientTags(ctx, session, cli)
return tm.svc.UpdateTags(ctx, session, cli)
}
// UpdateClientIdentity traces the "UpdateClientIdentity" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) UpdateClientIdentity(ctx context.Context, session authn.Session, id, identity string) (mgclients.Client, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_client_identity", trace.WithAttributes(
// UpdateEmail traces the "UpdateEmail" operation of the wrapped users.Service.
func (tm *tracingMiddleware) UpdateEmail(ctx context.Context, session authn.Session, id, email string) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_user_email", trace.WithAttributes(
attribute.String("id", id),
attribute.String("identity", identity),
attribute.String("email", email),
))
defer span.End()
return tm.svc.UpdateClientIdentity(ctx, session, id, identity)
return tm.svc.UpdateEmail(ctx, session, id, email)
}
// UpdateClientSecret traces the "UpdateClientSecret" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) UpdateClientSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (mgclients.Client, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_client_secret")
// UpdateSecret traces the "UpdateSecret" operation of the wrapped users.Service.
func (tm *tracingMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_user_secret")
defer span.End()
return tm.svc.UpdateClientSecret(ctx, session, oldSecret, newSecret)
return tm.svc.UpdateSecret(ctx, session, oldSecret, newSecret)
}
// GenerateResetToken traces the "GenerateResetToken" operation of the wrapped clients.Service.
// UpdateUsername traces the "UpdateUsername" operation of the wrapped users.Service.
func (tm *tracingMiddleware) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_usernames", trace.WithAttributes(
attribute.String("id", id),
attribute.String("username", username),
))
defer span.End()
return tm.svc.UpdateUsername(ctx, session, id, username)
}
// UpdateProfilePicture traces the "UpdateProfilePicture" operation of the wrapped users.Service.
func (tm *tracingMiddleware) UpdateProfilePicture(ctx context.Context, session authn.Session, usr users.User) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_profile_picture", trace.WithAttributes(attribute.String("id", usr.ID)))
defer span.End()
return tm.svc.Update(ctx, session, usr)
}
// GenerateResetToken traces the "GenerateResetToken" operation of the wrapped users.Service.
func (tm *tracingMiddleware) GenerateResetToken(ctx context.Context, email, host string) error {
ctx, span := tm.tracer.Start(ctx, "svc_generate_reset_token", trace.WithAttributes(
attribute.String("email", email),
@@ -137,7 +156,7 @@ func (tm *tracingMiddleware) GenerateResetToken(ctx context.Context, email, host
return tm.svc.GenerateResetToken(ctx, email, host)
}
// ResetSecret traces the "ResetSecret" operation of the wrapped clients.Service.
// ResetSecret traces the "ResetSecret" operation of the wrapped users.Service.
func (tm *tracingMiddleware) ResetSecret(ctx context.Context, session authn.Session, secret string) error {
ctx, span := tm.tracer.Start(ctx, "svc_reset_secret")
defer span.End()
@@ -145,7 +164,7 @@ func (tm *tracingMiddleware) ResetSecret(ctx context.Context, session authn.Sess
return tm.svc.ResetSecret(ctx, session, secret)
}
// SendPasswordReset traces the "SendPasswordReset" operation of the wrapped clients.Service.
// SendPasswordReset traces the "SendPasswordReset" operation of the wrapped users.Service.
func (tm *tracingMiddleware) SendPasswordReset(ctx context.Context, host, email, user, token string) error {
ctx, span := tm.tracer.Start(ctx, "svc_send_password_reset", trace.WithAttributes(
attribute.String("email", email),
@@ -156,50 +175,50 @@ func (tm *tracingMiddleware) SendPasswordReset(ctx context.Context, host, email,
return tm.svc.SendPasswordReset(ctx, host, email, user, token)
}
// ViewProfile traces the "ViewProfile" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) ViewProfile(ctx context.Context, session authn.Session) (mgclients.Client, error) {
// ViewProfile traces the "ViewProfile" operation of the wrapped users.Service.
func (tm *tracingMiddleware) ViewProfile(ctx context.Context, session authn.Session) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_view_profile")
defer span.End()
return tm.svc.ViewProfile(ctx, session)
}
// UpdateClientRole traces the "UpdateClientRole" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) UpdateClientRole(ctx context.Context, session authn.Session, cli mgclients.Client) (mgclients.Client, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_client_role", trace.WithAttributes(
// UpdateRole traces the "UpdateRole" operation of the wrapped users.Service.
func (tm *tracingMiddleware) UpdateRole(ctx context.Context, session authn.Session, cli users.User) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_update_user_role", trace.WithAttributes(
attribute.String("id", cli.ID),
attribute.StringSlice("tags", cli.Tags),
))
defer span.End()
return tm.svc.UpdateClientRole(ctx, session, cli)
return tm.svc.UpdateRole(ctx, session, cli)
}
// EnableClient traces the "EnableClient" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) EnableClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
ctx, span := tm.tracer.Start(ctx, "svc_enable_client", trace.WithAttributes(attribute.String("id", id)))
// Enable traces the "Enable" operation of the wrapped users.Service.
func (tm *tracingMiddleware) Enable(ctx context.Context, session authn.Session, id string) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_enable_user", trace.WithAttributes(attribute.String("id", id)))
defer span.End()
return tm.svc.EnableClient(ctx, session, id)
return tm.svc.Enable(ctx, session, id)
}
// DisableClient traces the "DisableClient" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) DisableClient(ctx context.Context, session authn.Session, id string) (mgclients.Client, error) {
ctx, span := tm.tracer.Start(ctx, "svc_disable_client", trace.WithAttributes(attribute.String("id", id)))
// Disable traces the "Disable" operation of the wrapped users.Service.
func (tm *tracingMiddleware) Disable(ctx context.Context, session authn.Session, id string) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_disable_user", trace.WithAttributes(attribute.String("id", id)))
defer span.End()
return tm.svc.DisableClient(ctx, session, id)
return tm.svc.Disable(ctx, session, id)
}
// ListMembers traces the "ListMembers" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm mgclients.Page) (mgclients.MembersPage, error) {
// ListMembers traces the "ListMembers" operation of the wrapped users.Service.
func (tm *tracingMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (users.MembersPage, error) {
ctx, span := tm.tracer.Start(ctx, "svc_list_members", trace.WithAttributes(attribute.String("object_kind", objectKind)), trace.WithAttributes(attribute.String("object_id", objectID)))
defer span.End()
return tm.svc.ListMembers(ctx, session, objectKind, objectID, pm)
}
// Identify traces the "Identify" operation of the wrapped clients.Service.
// Identify traces the "Identify" operation of the wrapped users.Service.
func (tm *tracingMiddleware) Identify(ctx context.Context, session authn.Session) (string, error) {
ctx, span := tm.tracer.Start(ctx, "svc_identify", trace.WithAttributes(attribute.String("user_id", session.UserID)))
defer span.End()
@@ -207,30 +226,30 @@ func (tm *tracingMiddleware) Identify(ctx context.Context, session authn.Session
return tm.svc.Identify(ctx, session)
}
// OAuthCallback traces the "OAuthCallback" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) OAuthCallback(ctx context.Context, client mgclients.Client) (mgclients.Client, error) {
// OAuthCallback traces the "OAuthCallback" operation of the wrapped users.Service.
func (tm *tracingMiddleware) OAuthCallback(ctx context.Context, user users.User) (users.User, error) {
ctx, span := tm.tracer.Start(ctx, "svc_oauth_callback", trace.WithAttributes(
attribute.String("client_id", client.ID),
attribute.String("user_id", user.ID),
))
defer span.End()
return tm.svc.OAuthCallback(ctx, client)
return tm.svc.OAuthCallback(ctx, user)
}
// DeleteClient traces the "DeleteClient" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) DeleteClient(ctx context.Context, session authn.Session, id string) error {
ctx, span := tm.tracer.Start(ctx, "svc_delete_client", trace.WithAttributes(attribute.String("id", id)))
// Delete traces the "Delete" operation of the wrapped users.Service.
func (tm *tracingMiddleware) Delete(ctx context.Context, session authn.Session, id string) error {
ctx, span := tm.tracer.Start(ctx, "svc_delete_user", trace.WithAttributes(attribute.String("id", id)))
defer span.End()
return tm.svc.DeleteClient(ctx, session, id)
return tm.svc.Delete(ctx, session, id)
}
// OAuthAddClientPolicy traces the "OAuthAddClientPolicy" operation of the wrapped clients.Service.
func (tm *tracingMiddleware) OAuthAddClientPolicy(ctx context.Context, client mgclients.Client) error {
ctx, span := tm.tracer.Start(ctx, "svc_add_client_policy", trace.WithAttributes(
attribute.String("id", client.ID),
// OAuthAddUserPolicy traces the "OAuthAddUserPolicy" operation of the wrapped users.Service.
func (tm *tracingMiddleware) OAuthAddUserPolicy(ctx context.Context, user users.User) error {
ctx, span := tm.tracer.Start(ctx, "svc_add_user_policy", trace.WithAttributes(
attribute.String("id", user.ID),
))
defer span.End()
return tm.svc.OAuthAddClientPolicy(ctx, client)
return tm.svc.OAuthAddUserPolicy(ctx, user)
}
+218
View File
@@ -0,0 +1,218 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package users
import (
"context"
"net/mail"
"time"
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/pkg/authn"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/magistrala/pkg/postgres"
)
type User struct {
ID string `json:"id"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Tags []string `json:"tags,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
Status Status `json:"status"` // 0 for enabled, 1 for disabled
Role Role `json:"role"` // 0 for normal user, 1 for admin
ProfilePicture string `json:"profile_picture,omitempty"` // profile picture URL
Credentials Credentials `json:"credentials,omitempty"`
Permissions []string `json:"permissions,omitempty"`
Email string `json:"email,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
}
type Credentials struct {
Username string `json:"username,omitempty"` // username or profile name
Secret string `json:"secret,omitempty"` // password or token
}
type UsersPage struct {
Page
Users []User
}
// Metadata represents arbitrary JSON.
type Metadata map[string]interface{}
// MembersPage contains page related metadata as well as list of members that
// belong to this page.
type MembersPage struct {
Page
Members []User
}
// UserRepository struct implements the Repository interface.
type UserRepository struct {
DB postgres.Database
}
//go:generate mockery --name Repository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines"
type Repository interface {
// RetrieveByID retrieves user by their unique ID.
RetrieveByID(ctx context.Context, id string) (User, error)
// RetrieveAll retrieves all users.
RetrieveAll(ctx context.Context, pm Page) (UsersPage, error)
// RetrieveByEmail retrieves user by its unique credentials.
RetrieveByEmail(ctx context.Context, email string) (User, error)
// RetrieveByUsername retrieves user by its unique credentials.
RetrieveByUsername(ctx context.Context, username string) (User, error)
// Update updates the user name and metadata.
Update(ctx context.Context, user User) (User, error)
// UpdateUsername updates the User's names.
UpdateUsername(ctx context.Context, user User) (User, error)
// UpdateSecret updates secret for user with given email.
UpdateSecret(ctx context.Context, user User) (User, error)
// ChangeStatus changes user status to enabled or disabled
ChangeStatus(ctx context.Context, user User) (User, error)
// Delete deletes user with given id
Delete(ctx context.Context, id string) error
// Searchusers retrieves users based on search criteria.
SearchUsers(ctx context.Context, pm Page) (UsersPage, error)
// RetrieveAllByIDs retrieves for given user IDs .
RetrieveAllByIDs(ctx context.Context, pm Page) (UsersPage, error)
CheckSuperAdmin(ctx context.Context, adminID string) error
// Save persists the user account. A non-nil error is returned to indicate
// operation failure.
Save(ctx context.Context, user User) (User, error)
}
// Validate returns an error if user representation is invalid.
func (u User) Validate() error {
if !isEmail(u.Email) {
return errors.ErrMalformedEntity
}
return nil
}
func isEmail(email string) bool {
_, err := mail.ParseAddress(email)
return err == nil
}
// Page contains page metadata that helps navigation.
type Page struct {
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Id string `json:"id,omitempty"`
Order string `json:"order,omitempty"`
Dir string `json:"dir,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
Domain string `json:"domain,omitempty"`
Tag string `json:"tag,omitempty"`
Permission string `json:"permission,omitempty"`
Status Status `json:"status,omitempty"`
IDs []string `json:"ids,omitempty"`
Role Role `json:"-"`
ListPerms bool `json:"-"`
Username string `json:"username,omitempty"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Email string `json:"email,omitempty"`
}
// Service specifies an API that must be fullfiled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
//
//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines"
type Service interface {
// Register creates new user. In case of the failed registration, a
// non-nil error value is returned.
Register(ctx context.Context, session authn.Session, user User, selfRegister bool) (User, error)
// View retrieves user info for a given user ID and an authorized token.
View(ctx context.Context, session authn.Session, id string) (User, error)
// ViewProfile retrieves user info for a given token.
ViewProfile(ctx context.Context, session authn.Session) (User, error)
// ListUsers retrieves users list for a valid auth token.
ListUsers(ctx context.Context, session authn.Session, pm Page) (UsersPage, error)
// ListMembers retrieves everything that is assigned to a group/thing identified by objectID.
ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm Page) (MembersPage, error)
// SearchUsers searches for users with provided filters for a valid auth token.
SearchUsers(ctx context.Context, pm Page) (UsersPage, error)
// Update updates the user's name and metadata.
Update(ctx context.Context, session authn.Session, user User) (User, error)
// UpdateTags updates the user's tags.
UpdateTags(ctx context.Context, session authn.Session, user User) (User, error)
// UpdateEmail updates the user's email.
UpdateEmail(ctx context.Context, session authn.Session, id, email string) (User, error)
// UpdateUsername updates the user's username.
UpdateUsername(ctx context.Context, session authn.Session, id, username string) (User, error)
// UpdateProfile updates the user's profile picture.
UpdateProfilePicture(ctx context.Context, session authn.Session, user User) (User, error)
// GenerateResetToken email where mail will be sent.
// host is used for generating reset link.
GenerateResetToken(ctx context.Context, email, host string) error
// UpdateSecret updates the user's secret.
UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (User, error)
// ResetSecret change users secret in reset flow.
// token can be authentication token or secret reset token.
ResetSecret(ctx context.Context, session authn.Session, secret string) error
// SendPasswordReset sends reset password link to email.
SendPasswordReset(ctx context.Context, host, email, user, token string) error
// UpdateRole updates the user's Role.
UpdateRole(ctx context.Context, session authn.Session, user User) (User, error)
// Enable logically enables the user identified with the provided ID.
Enable(ctx context.Context, session authn.Session, id string) (User, error)
// Disable logically disables the user identified with the provided ID.
Disable(ctx context.Context, session authn.Session, id string) (User, error)
// Delete deletes user with given ID.
Delete(ctx context.Context, session authn.Session, id string) error
// Identify returns the user id from the given token.
Identify(ctx context.Context, session authn.Session) (string, error)
// IssueToken issues a new access and refresh token when provided with either a username or email.
IssueToken(ctx context.Context, identity, secret string) (*magistrala.Token, error)
// RefreshToken refreshes expired access tokens.
// After an access token expires, the refresh token is used to get
// a new pair of access and refresh tokens.
RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error)
// OAuthCallback handles the callback from any supported OAuth provider.
// It processes the OAuth tokens and either signs in or signs up the user based on the provided state.
OAuthCallback(ctx context.Context, user User) (User, error)
// OAuthAddUserPolicy adds a policy to the user for an OAuth request.
OAuthAddUserPolicy(ctx context.Context, user User) error
}