Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add account sharing feature to MGC CLI. #1267

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mgc/core/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type Config struct {
ClientId string
ObjectStoreScopeIDs []string
PublicClientsScopeIDs map[string]string
TotalScopeID string
RedirectUri string
LoginUrl string
TokenUrl string
Expand All @@ -85,6 +86,8 @@ type Config struct {
ApiKeysUrlV2 string
PublicClientsUrl string
ClientsV2Url string
DelegationUrl string
AccountsUrl string
}

type Authenticator interface {
Expand Down
6 changes: 6 additions & 0 deletions mgc/sdk/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ func init() {
ClientId: "cw9qpaUl2nBiC8PVjNFN5jZeb2vTd_1S5cYs1FhEXh0",
ObjectStoreScopeIDs: []string{"b6afac7e-0afd-42de-b4aa-1bc82a27e307", "5ea6d1f7-20eb-4e80-9a9c-c7923636a4bd"},
PublicClientsScopeIDs: map[string]string{"openid": "2836b3ba-093c-416a-92f0-7fc4ee5ac961", "profile": "50447cbf-8a42-4426-8e53-fe84bf0726ad"},
TotalScopeID: "4939109a-1b47-4c8a-936c-8ed2e162bd98",
RedirectUri: "http://localhost:8095/callback",
LoginUrl: "https://id.magalu.com/login",
TokenUrl: "https://id.magalu.com/oauth/token",
Expand All @@ -227,11 +228,14 @@ func init() {
ApiKeysUrlV2: "https://id.magalu.com/account/api/v2/api-keys",
PublicClientsUrl: "https://id.magalu.com/account/api/v1/external/clients",
ClientsV2Url: "https://id.magalu.com/account/api/v2/clients",
DelegationUrl: "https://id.magalu.com/account/api/v1/delegations",
AccountsUrl: "https://id.magalu.com/account/api/v1/accounts",
},
"pre-prod": { // TODO update this links to the correct ones
ClientId: "dByqQVtHcs07b_O9jpUDgfV5UCskh9TbC64WUXEdVHE",
ObjectStoreScopeIDs: []string{"b6afac7e-0afd-42de-b4aa-1bc82a27e307", "5ea6d1f7-20eb-4e80-9a9c-c7923636a4bd"},
PublicClientsScopeIDs: map[string]string{"openid": "4bdb7c8e-6006-478a-ba90-f8313f88bbb8", "profile": "8614f807-9aea-462c-bade-6c08fa52a272"},
TotalScopeID: "FIX-IT",
RedirectUri: "http://localhost:8095/callback",
LoginUrl: "https://idmagalu-preprod.luizalabs.com/login",
TokenUrl: "https://idpa-api-preprod.luizalabs.com/oauth/token",
Expand All @@ -243,6 +247,8 @@ func init() {
ApiKeysUrlV2: "https://platform-account-api-preprod.luizalabs.com/api/v2/api-keys",
PublicClientsUrl: "https://platform-account-api-preprod.luizalabs.com/api/v1/external/clients",
ClientsV2Url: "https://platform-account-api-preprod.luizalabs.com/api/v2/clients",
DelegationUrl: "https://platform-account-api-preprod.luizalabs.com/api/v1/delegations",
AccountsUrl: "https://platform-account-api-preprod.luizalabs.com/api/v1/accounts",
},
}
authConfigMap["default"] = authConfigMap["prod"]
Expand Down
2 changes: 2 additions & 0 deletions mgc/sdk/static/auth/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"magalu.cloud/core/utils"
"magalu.cloud/sdk/static/auth/api_key"
"magalu.cloud/sdk/static/auth/clients"
"magalu.cloud/sdk/static/auth/sharing"
"magalu.cloud/sdk/static/auth/tenant"
)

Expand All @@ -26,6 +27,7 @@ of HTTP requests using the MgcSDK. Authentication is done via Magalu Cloud accou
tenant.GetGroup(),
clients.GetGroup(),
api_key.GetGroup(),
sharing.GetGroup(),
}
},
)
Expand Down
4 changes: 4 additions & 0 deletions mgc/sdk/static/auth/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ func login(ctx context.Context, parameters loginParameters, _ struct{}) (*loginR

scopes.Add("evt:event-tr")
scopes.Add("pa:sa:manage")
scopes.Add("pa:accounts:read")
scopes.Add("pa:delegations:read")
scopes.Add("pa:delegations:write")
scopes.Add("pa:delegations:delete")
}

codeUrl, err := auth.CodeChallengeToURL(scopes)
Expand Down
124 changes: 124 additions & 0 deletions mgc/sdk/static/auth/sharing/admit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package sharing

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"

"magalu.cloud/core"
mgcAuthPkg "magalu.cloud/core/auth"
mgcHttpPkg "magalu.cloud/core/http"
"magalu.cloud/core/utils"
)

type createParams struct {
DelegateToEmail string `json:"email" jsonschema:"description=User email" mgc:"positional"`
}

type Account struct {
Email string `json:"email"`
Name string `json:"name"`
UUID string `json:"uuid"`
}

type createPayload struct {
DelegateToUUID string `json:"delegated_to" jsonschema:"description=User UUID" mgc:"positional"`
ScopeID string `json:"scope_id"`
IsAdmin bool `json:"is_admin"`
}

type delegateResult struct {
DelegationID string `json:"delegation_id"`
ScopeDelegationID string `json:"scope_delegation_id"`
}

var getCreate = utils.NewLazyLoader[core.Executor](func() core.Executor {
executor := core.NewStaticExecute(
core.DescriptorSpec{
Name: "admit",
Description: "Admit access to my Account/Organization",
},
create,
)

return executor
})

func create(ctx context.Context, parameter createParams, _ struct{}) (*delegateResult, error) {
auth := mgcAuthPkg.FromContext(ctx)
if auth == nil {
return nil, fmt.Errorf("programming error: unable to retrieve auth configuration from context")
}

httpClient := auth.AuthenticatedHttpClientFromContext(ctx)
if httpClient == nil {
return nil, fmt.Errorf("programming error: unable to retrieve HTTP Client from context")
}

config := auth.GetConfig()

//////////////////////////////////////////////////////////////
// Find Account ID
r, err := http.NewRequestWithContext(ctx, http.MethodGet, config.AccountsUrl+"?email="+parameter.DelegateToEmail, nil)
if err != nil {
return nil, err
}
r.Header.Set("Content-Type", "application/json")
resp, err := httpClient.Do(r)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected response")
}

defer resp.Body.Close()
var account_result []*Account
if err = json.NewDecoder(resp.Body).Decode(&account_result); err != nil {
return nil, err
}

if len(account_result) < 1 {
return nil, fmt.Errorf("user account not found")
}

DelegateToUUID := account_result[0].UUID
//////////////////////////////////////////////////////////////
clientPayload := createPayload{
DelegateToUUID: DelegateToUUID,
ScopeID: config.TotalScopeID,
IsAdmin: false,
}

var buf bytes.Buffer
err = json.NewEncoder(&buf).Encode(clientPayload)
if err != nil {
return nil, err
}

r, err = http.NewRequestWithContext(ctx, http.MethodPost, config.DelegationUrl, &buf)
if err != nil {
return nil, err
}

r.Header.Set("Content-Type", "application/json")
resp, err = httpClient.Do(r)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, mgcHttpPkg.NewHttpErrorFromResponse(resp, r)
}

defer resp.Body.Close()
var result delegateResult
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}

return &result, nil
}
23 changes: 23 additions & 0 deletions mgc/sdk/static/auth/sharing/group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package sharing

import (
"magalu.cloud/core"
"magalu.cloud/core/utils"
)

var GetGroup = utils.NewLazyLoader(func() core.Grouper {
return core.NewStaticGroup(
core.DescriptorSpec{
Name: "sharing",
Summary: "Shared Access (Beta)",
Description: `You may share access to your account or organization with other people using Cloud Access Share`,
},
func() []core.Descriptor {
return []core.Descriptor{
getList(),
getCreate(),
getRemove(),
}
},
)
})
62 changes: 62 additions & 0 deletions mgc/sdk/static/auth/sharing/refuse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package sharing

import (
"context"
"fmt"
"net/http"

"magalu.cloud/core"
mgcAuthPkg "magalu.cloud/core/auth"
mgcHttpPkg "magalu.cloud/core/http"
"magalu.cloud/core/utils"
)

type removeParams struct {
DelegationID string `json:"uuid" jsonschema:"description=Shared uuid" mgc:"positional"`
}

var getRemove = utils.NewLazyLoader[core.Executor](func() core.Executor {
executor := core.NewStaticExecute(
core.DescriptorSpec{
Name: "refuse",
Description: "Refuse access to my Account/Organization",
},
remove,
)

return executor
})

func remove(ctx context.Context, parameter removeParams, _ struct{}) (*[]Delegation, error) {
auth := mgcAuthPkg.FromContext(ctx)
if auth == nil {
return nil, fmt.Errorf("programming error: unable to retrieve auth configuration from context")
}

httpClient := auth.AuthenticatedHttpClientFromContext(ctx)
if httpClient == nil {
return nil, fmt.Errorf("programming error: unable to retrieve HTTP Client from context")
}

config := auth.GetConfig()

r, err := http.NewRequestWithContext(ctx, http.MethodDelete, config.DelegationUrl+"/"+parameter.DelegationID, nil)
if err != nil {
return nil, err
}

r.Header.Set("Content-Type", "application/json")
resp, err := httpClient.Do(r)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, mgcHttpPkg.NewHttpErrorFromResponse(resp, r)
}

defer resp.Body.Close()

var result []Delegation
return &result, nil
}
88 changes: 88 additions & 0 deletions mgc/sdk/static/auth/sharing/show.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package sharing

import (
"context"
"encoding/json"
"fmt"
"net/http"

"magalu.cloud/core"
mgcAuthPkg "magalu.cloud/core/auth"
mgcHttpPkg "magalu.cloud/core/http"
"magalu.cloud/core/utils"
)

type DelegationAccount struct {
Name string `json:"name"`
Email string `json:"email"`
}

type DelegationTenant struct {
LegalName string `json:"legal_name"`
}

type Delegation struct {
UUID string `json:"uuid"`
DelegatedAt string `json:"delegated_at"`
DelegatedBy DelegationAccount `json:"delegated_by"`
DelegatedTo DelegationAccount `json:"delegated_to"`
Tentant DelegationTenant `json:"tenant"`
}

var getList = utils.NewLazyLoader[core.Executor](func() core.Executor {
var exec core.Executor = core.NewStaticExecuteSimple(
core.DescriptorSpec{
Name: "show",
Description: "Show people with access to my Account/Organization",
},
listDelegations,
)

return exec
})

func listDelegations(ctx context.Context) ([]*Delegation, error) {
auth := mgcAuthPkg.FromContext(ctx)
if auth == nil {
return nil, fmt.Errorf("programming error: unable to get auth from context")
}
res, err := ListDelegations(ctx)
return res, err
}

func ListDelegations(ctx context.Context) ([]*Delegation, error) {
auth := mgcAuthPkg.FromContext(ctx)
httpClient := auth.AuthenticatedHttpClientFromContext(ctx)
if httpClient == nil {
return nil, fmt.Errorf("programming error: unable to get HTTP Client from context")
}

r, err := http.NewRequestWithContext(ctx, http.MethodGet, auth.GetConfig().DelegationUrl, nil)
if err != nil {
return nil, err
}
r.Header.Set("Content-Type", "application/json")

resp, err := httpClient.Do(r)
if err != nil {
return nil, err
}

if resp.StatusCode == http.StatusOK {
defer resp.Body.Close()
var result []*Delegation
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}

return result, nil
}

if resp.StatusCode == http.StatusNoContent {
var result []*Delegation
return result, nil
}

return nil, mgcHttpPkg.NewHttpErrorFromResponse(resp, r)

}