basic auth delegated token poc
This commit is contained in:
parent
c6464555dd
commit
0e83120428
@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/alcionai/corso/src/cli/backup"
|
||||
@ -33,10 +34,10 @@ var corsoCmd = &cobra.Command{
|
||||
Short: "Free, Secure, Open-Source Backup for M365.",
|
||||
Long: `Free, Secure, and Open-Source Backup for Microsoft 365.`,
|
||||
RunE: handleCorsoCmd,
|
||||
PersistentPreRunE: preRun,
|
||||
PersistentPreRunE: PreRun,
|
||||
}
|
||||
|
||||
func preRun(cc *cobra.Command, args []string) error {
|
||||
func PreRun(cc *cobra.Command, args []string) error {
|
||||
if err := config.InitFunc(cc, args); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -104,6 +105,13 @@ func CorsoCommand() *cobra.Command {
|
||||
return c
|
||||
}
|
||||
|
||||
func AddSupportFlags(cmd *cobra.Command) {
|
||||
config.AddConfigFlags(cmd)
|
||||
logger.AddLoggingFlags(cmd)
|
||||
observe.AddProgressBarFlags(cmd)
|
||||
print.AddOutputFlag(cmd)
|
||||
}
|
||||
|
||||
// BuildCommandTree builds out the command tree used by the Corso library.
|
||||
func BuildCommandTree(cmd *cobra.Command) {
|
||||
// want to order flags explicitly
|
||||
@ -111,11 +119,8 @@ func BuildCommandTree(cmd *cobra.Command) {
|
||||
flags.AddRunModeFlag(cmd, true)
|
||||
|
||||
cmd.Flags().BoolP("version", "v", false, "current version info")
|
||||
cmd.PersistentPreRunE = preRun
|
||||
config.AddConfigFlags(cmd)
|
||||
logger.AddLoggingFlags(cmd)
|
||||
observe.AddProgressBarFlags(cmd)
|
||||
print.AddOutputFlag(cmd)
|
||||
cmd.PersistentPreRunE = PreRun
|
||||
AddSupportFlags(cmd)
|
||||
flags.AddGlobalOperationFlags(cmd)
|
||||
cmd.SetUsageTemplate(indentExamplesTemplate(corsoCmd.UsageTemplate()))
|
||||
|
||||
@ -132,14 +137,20 @@ func BuildCommandTree(cmd *cobra.Command) {
|
||||
// Running Corso
|
||||
// ------------------------------------------------------------------------------------------
|
||||
|
||||
// Handle builds and executes the cli processor.
|
||||
func Handle() {
|
||||
func SeedCtx() (context.Context, *zap.SugaredLogger) {
|
||||
//nolint:forbidigo
|
||||
ctx := config.Seed(context.Background())
|
||||
ctx, log := logger.Seed(ctx, logger.PreloadLoggingFlags(os.Args[1:]))
|
||||
ctx = print.SetRootCmd(ctx, corsoCmd)
|
||||
ctx = observe.SeedObserver(ctx, print.StderrWriter(ctx), observe.PreloadFlags())
|
||||
|
||||
return ctx, log
|
||||
}
|
||||
|
||||
// Handle builds and executes the cli processor.
|
||||
func Handle() {
|
||||
ctx, log := SeedCtx()
|
||||
|
||||
BuildCommandTree(corsoCmd)
|
||||
|
||||
defer func() {
|
||||
|
||||
@ -98,17 +98,16 @@ func configureAccount(
|
||||
|
||||
// M365 is a helper for aggregating m365 secrets and credentials.
|
||||
func GetM365(m365Cfg account.M365Config) credentials.M365 {
|
||||
AzureClientID := str.First(
|
||||
creds := credentials.GetM365()
|
||||
|
||||
creds.AzureClientID = str.First(
|
||||
flags.AzureClientIDFV,
|
||||
os.Getenv(credentials.AzureClientID),
|
||||
creds.AzureClientID,
|
||||
m365Cfg.AzureClientID)
|
||||
AzureClientSecret := str.First(
|
||||
creds.AzureClientSecret = str.First(
|
||||
flags.AzureClientSecretFV,
|
||||
os.Getenv(credentials.AzureClientSecret),
|
||||
creds.AzureClientSecret,
|
||||
m365Cfg.AzureClientSecret)
|
||||
|
||||
return credentials.M365{
|
||||
AzureClientID: AzureClientID,
|
||||
AzureClientSecret: AzureClientSecret,
|
||||
}
|
||||
return creds
|
||||
}
|
||||
|
||||
73
src/cmd/delegated/delegated.go
Normal file
73
src/cmd/delegated/delegated.go
Normal file
@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/alcionai/corso/src/cli"
|
||||
. "github.com/alcionai/corso/src/cli/print"
|
||||
"github.com/alcionai/corso/src/cli/utils"
|
||||
"github.com/alcionai/corso/src/internal/observe"
|
||||
"github.com/alcionai/corso/src/pkg/count"
|
||||
"github.com/alcionai/corso/src/pkg/path"
|
||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||
)
|
||||
|
||||
// The root-level command.
|
||||
// `corso <command> [<subcommand>] [<service>] [<flag>...]`
|
||||
var cmd = &cobra.Command{
|
||||
Use: "delegated",
|
||||
Short: "delegated token POC",
|
||||
RunE: getToken,
|
||||
PersistentPreRunE: cli.PreRun,
|
||||
}
|
||||
|
||||
func main() {
|
||||
cli.AddSupportFlags(cmd)
|
||||
ctx, log := cli.SeedCtx()
|
||||
|
||||
defer func() {
|
||||
observe.Flush(ctx) // flush the progress bars
|
||||
|
||||
_ = log.Sync() // flush all logs in the buffer
|
||||
}()
|
||||
|
||||
if err := cmd.ExecuteContext(ctx); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func getToken(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
_, details, err := utils.GetAccountAndConnect(
|
||||
ctx,
|
||||
cmd,
|
||||
path.ExchangeService)
|
||||
if err != nil {
|
||||
return Only(ctx, err)
|
||||
}
|
||||
|
||||
creds, err := details.Repo.Account.M365Config()
|
||||
if err != nil {
|
||||
return Only(ctx, err)
|
||||
}
|
||||
|
||||
ac, err := api.NewClient(
|
||||
creds,
|
||||
details.Opts,
|
||||
count.New())
|
||||
if err != nil {
|
||||
return Only(ctx, err)
|
||||
}
|
||||
|
||||
da, err := ac.Access().GetDelegatedToken(ctx)
|
||||
if err != nil {
|
||||
return Only(ctx, err)
|
||||
}
|
||||
|
||||
PrettyJSON(ctx, da)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -21,6 +21,8 @@ const (
|
||||
keyAzureClientID = "azure_clientid"
|
||||
keyAzureClientSecret = "azure_clientSecret"
|
||||
keyAzureTenantID = "azure_tenantid"
|
||||
keyAzureUsername = "azure_username"
|
||||
keyAzureUserPassword = "azure_userPassword"
|
||||
)
|
||||
|
||||
// StringConfig transforms a m365Config struct into a plain
|
||||
@ -31,6 +33,8 @@ func (c M365Config) StringConfig() (map[string]string, error) {
|
||||
keyAzureClientID: c.AzureClientID,
|
||||
keyAzureClientSecret: c.AzureClientSecret,
|
||||
keyAzureTenantID: c.AzureTenantID,
|
||||
keyAzureUsername: c.AzureUsername,
|
||||
keyAzureUserPassword: c.AzureUserPassword,
|
||||
}
|
||||
|
||||
return cfg, c.validate()
|
||||
@ -52,6 +56,8 @@ func (a Account) M365Config() (M365Config, error) {
|
||||
c.AzureClientID = a.Config[keyAzureClientID]
|
||||
c.AzureClientSecret = a.Config[keyAzureClientSecret]
|
||||
c.AzureTenantID = a.Config[keyAzureTenantID]
|
||||
c.AzureUsername = a.Config[keyAzureUsername]
|
||||
c.AzureUserPassword = a.Config[keyAzureUserPassword]
|
||||
}
|
||||
|
||||
return c, c.validate()
|
||||
|
||||
@ -10,12 +10,16 @@ import (
|
||||
const (
|
||||
AzureClientID = "AZURE_CLIENT_ID"
|
||||
AzureClientSecret = "AZURE_CLIENT_SECRET"
|
||||
AzureUsername = "AZURE_USERNAME"
|
||||
AzureUserPassword = "AZURE_USER_PASSWORD"
|
||||
)
|
||||
|
||||
// M365 aggregates m365 credentials from flag and env_var values.
|
||||
type M365 struct {
|
||||
AzureClientID string
|
||||
AzureClientSecret string
|
||||
AzureUsername string
|
||||
AzureUserPassword string
|
||||
}
|
||||
|
||||
// M365 is a helper for aggregating m365 secrets and credentials.
|
||||
@ -24,10 +28,14 @@ func GetM365() M365 {
|
||||
// var AzureClientID, AzureClientSecret string
|
||||
AzureClientID := os.Getenv(AzureClientID)
|
||||
AzureClientSecret := os.Getenv(AzureClientSecret)
|
||||
AzureUserPassword := os.Getenv(AzureUserPassword)
|
||||
AzureUsername := os.Getenv(AzureUsername)
|
||||
|
||||
return M365{
|
||||
AzureClientID: AzureClientID,
|
||||
AzureClientSecret: AzureClientSecret,
|
||||
AzureUsername: AzureUsername,
|
||||
AzureUserPassword: AzureUserPassword,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -15,8 +16,8 @@ import (
|
||||
// controller
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (c Client) Access() Access {
|
||||
return Access{c}
|
||||
func (c Client) Access() *Access {
|
||||
return &Access{c}
|
||||
}
|
||||
|
||||
// Access is an interface-compliant provider of the client.
|
||||
@ -66,3 +67,64 @@ func (c Access) GetToken(
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type delegatedAccess struct {
|
||||
TokenType string `json:"token_type"`
|
||||
Scope string `json:"scope"`
|
||||
ExpiresIn string `json:"expires_in"`
|
||||
ExpiresOn string `json:"expires_on"`
|
||||
NotBefore string `json:"not_before"`
|
||||
Resource string `json:"resource"`
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
func (da delegatedAccess) MinimumPrintable() any {
|
||||
return da
|
||||
}
|
||||
|
||||
func (c *Access) GetDelegatedToken(
|
||||
ctx context.Context,
|
||||
) (delegatedAccess, error) {
|
||||
var (
|
||||
//nolint:lll
|
||||
// https://dzone.com/articles/getting-access-token-for-microsoft-graph-using-oau
|
||||
rawURL = fmt.Sprintf(
|
||||
"https://login.microsoftonline.com/%s/oauth2/token",
|
||||
c.Credentials.AzureTenantID)
|
||||
headers = map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}
|
||||
body = strings.NewReader(fmt.Sprintf(
|
||||
"client_id=%s"+
|
||||
"&client_secret=%s"+
|
||||
"&resource=https://graph.microsoft.com"+
|
||||
"&grant_type=password"+
|
||||
"&username=%s"+
|
||||
"&password=%s",
|
||||
c.Credentials.AzureClientID,
|
||||
c.Credentials.AzureClientSecret,
|
||||
c.Credentials.AzureUsername,
|
||||
c.Credentials.AzureUserPassword))
|
||||
)
|
||||
|
||||
resp, err := c.Post(ctx, rawURL, headers, body)
|
||||
if err != nil {
|
||||
return delegatedAccess{}, graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusBadRequest {
|
||||
return delegatedAccess{}, clues.New("incorrect tenant or credentials")
|
||||
}
|
||||
|
||||
if resp.StatusCode/100 == 4 || resp.StatusCode/100 == 5 {
|
||||
return delegatedAccess{}, clues.New("non-2xx response: " + resp.Status)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var da delegatedAccess
|
||||
err = json.NewDecoder(resp.Body).Decode(&da)
|
||||
|
||||
return da, clues.Wrap(err, "undecodable body").WithClues(ctx).OrNil()
|
||||
}
|
||||
|
||||
@ -40,6 +40,9 @@ type Client struct {
|
||||
// graph api client.
|
||||
Requester graph.Requester
|
||||
|
||||
delegated graph.Servicer
|
||||
delegatedAccess delegatedAccess
|
||||
|
||||
counter *count.Bus
|
||||
|
||||
options control.Options
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user