poc for obtaining a device token
This commit is contained in:
parent
af804fad20
commit
583caecf02
73
src/cmd/delegated copy/delegated.go
Normal file
73
src/cmd/delegated copy/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
|
||||||
|
}
|
||||||
126
src/cmd/device/device.go
Normal file
126
src/cmd/device/device.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
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: "device",
|
||||||
|
Short: "device token POC",
|
||||||
|
RunE: help,
|
||||||
|
PersistentPreRunE: cli.PreRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = &cobra.Command{
|
||||||
|
Use: "request",
|
||||||
|
Short: "request device token POC",
|
||||||
|
RunE: requestToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
var get = &cobra.Command{
|
||||||
|
Use: "get",
|
||||||
|
Short: "get device token POC",
|
||||||
|
RunE: getToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}()
|
||||||
|
|
||||||
|
cmd.AddCommand(request)
|
||||||
|
cmd.AddCommand(get)
|
||||||
|
|
||||||
|
if err := cmd.ExecuteContext(ctx); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func help(cmd *cobra.Command, args []string) error {
|
||||||
|
return cmd.Help()
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestToken(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().RequestDeviceToken(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
PrettyJSON(ctx, da)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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().GetDeviceToken(ctx, args[0])
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
PrettyJSON(ctx, da)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
@ -68,24 +69,50 @@ func (c Access) GetToken(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type delegatedAccess struct {
|
type delegatedResp struct {
|
||||||
TokenType string `json:"token_type"`
|
AccessToken string `json:"access_token,omitempty"`
|
||||||
Scope string `json:"scope"`
|
Devicecode string `json:"device_code,omitempty"`
|
||||||
ExpiresIn string `json:"expires_in"`
|
ExpiresIn string `json:"expires_in,omitempty"`
|
||||||
ExpiresOn string `json:"expires_on"`
|
ExpiresOn string `json:"expires_on,omitempty"`
|
||||||
NotBefore string `json:"not_before"`
|
Interval string `json:"interval,omitempty"`
|
||||||
Resource string `json:"resource"`
|
Message string `json:"message,omitempty"`
|
||||||
AccessToken string `json:"access_token"`
|
NotBefore string `json:"not_before,omitempty"`
|
||||||
RefreshToken string `json:"refresh_token"`
|
RefreshToken string `json:"refresh_token,omitempty"`
|
||||||
|
Resource string `json:"resource,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
TokenType string `json:"token_type,omitempty"`
|
||||||
|
UserCode string `json:"user_code,omitempty"`
|
||||||
|
VerificationURI string `json:"verification_uri,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (da delegatedAccess) MinimumPrintable() any {
|
func (da delegatedResp) MinimumPrintable() any {
|
||||||
|
return da
|
||||||
|
}
|
||||||
|
|
||||||
|
type deviceResp struct {
|
||||||
|
AccessToken string `json:"access_token,omitempty"`
|
||||||
|
Devicecode string `json:"device_code,omitempty"`
|
||||||
|
ExpiresIn int `json:"expires_in,omitempty"`
|
||||||
|
ExpiresOn string `json:"expires_on,omitempty"`
|
||||||
|
IDToken string `json:"id_token,omitempty"`
|
||||||
|
Interval int `json:"interval,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
NotBefore string `json:"not_before,omitempty"`
|
||||||
|
RefreshToken string `json:"refresh_token,omitempty"`
|
||||||
|
Resource string `json:"resource,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
TokenType string `json:"token_type,omitempty"`
|
||||||
|
UserCode string `json:"user_code,omitempty"`
|
||||||
|
VerificationURI string `json:"verification_uri,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da deviceResp) MinimumPrintable() any {
|
||||||
return da
|
return da
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Access) GetDelegatedToken(
|
func (c *Access) GetDelegatedToken(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (delegatedAccess, error) {
|
) (delegatedResp, error) {
|
||||||
var (
|
var (
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
// https://dzone.com/articles/getting-access-token-for-microsoft-graph-using-oau
|
// https://dzone.com/articles/getting-access-token-for-microsoft-graph-using-oau
|
||||||
@ -110,21 +137,124 @@ func (c *Access) GetDelegatedToken(
|
|||||||
|
|
||||||
resp, err := c.Post(ctx, rawURL, headers, body)
|
resp, err := c.Post(ctx, rawURL, headers, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return delegatedAccess{}, graph.Stack(ctx, err)
|
return delegatedResp{}, graph.Stack(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusBadRequest {
|
if resp.StatusCode == http.StatusBadRequest {
|
||||||
return delegatedAccess{}, clues.New("incorrect tenant or credentials")
|
return delegatedResp{}, clues.New("incorrect tenant or credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode/100 == 4 || resp.StatusCode/100 == 5 {
|
if resp.StatusCode/100 == 4 || resp.StatusCode/100 == 5 {
|
||||||
return delegatedAccess{}, clues.New("non-2xx response: " + resp.Status)
|
return delegatedResp{}, clues.New("non-2xx response: " + resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
var da delegatedAccess
|
var ar delegatedResp
|
||||||
err = json.NewDecoder(resp.Body).Decode(&da)
|
err = json.NewDecoder(resp.Body).Decode(&ar)
|
||||||
|
|
||||||
return da, clues.Wrap(err, "undecodable body").WithClues(ctx).OrNil()
|
return ar, clues.Wrap(err, "undecodable resp body").WithClues(ctx).OrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Access) RequestDeviceToken(
|
||||||
|
ctx context.Context,
|
||||||
|
) (deviceResp, 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/v2.0/devicecode",
|
||||||
|
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&scope=%s",
|
||||||
|
c.Credentials.AzureClientID,
|
||||||
|
c.Credentials.AzureClientSecret,
|
||||||
|
"user.read openid profile offline_access"))
|
||||||
|
)
|
||||||
|
|
||||||
|
resp, err := c.Post(ctx, rawURL, headers, body)
|
||||||
|
if err != nil {
|
||||||
|
return deviceResp{}, graph.Stack(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusBadRequest {
|
||||||
|
return deviceResp{}, clues.New("incorrect tenant or credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode/100 == 4 || resp.StatusCode/100 == 5 {
|
||||||
|
return deviceResp{}, clues.New("non-2xx response: " + resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var ar deviceResp
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&ar)
|
||||||
|
|
||||||
|
return ar, clues.Wrap(err, "undecodable resp body").WithClues(ctx).OrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Access) GetDeviceToken(
|
||||||
|
ctx context.Context,
|
||||||
|
deviceCode string,
|
||||||
|
) (deviceResp, 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/v2.0/token",
|
||||||
|
c.Credentials.AzureTenantID)
|
||||||
|
headers = map[string]string{
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
}
|
||||||
|
body = strings.NewReader(fmt.Sprintf(
|
||||||
|
"grant_type=urn:ietf:params:oauth:grant-type:device_code"+
|
||||||
|
"&client_id=%s"+
|
||||||
|
"&client_secret=%s"+
|
||||||
|
"&device_code=%s",
|
||||||
|
c.Credentials.AzureClientID,
|
||||||
|
c.Credentials.AzureClientSecret,
|
||||||
|
deviceCode))
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Printf("\n-----\ndc %q\n-----\n", deviceCode)
|
||||||
|
|
||||||
|
resp, err := c.Post(ctx, rawURL, headers, body)
|
||||||
|
if err != nil {
|
||||||
|
err = graph.Stack(ctx, err)
|
||||||
|
fmt.Printf("\n-----\nERROR %+v\n-----\n", clues.ToCore(err))
|
||||||
|
|
||||||
|
return deviceResp{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusBadRequest {
|
||||||
|
respDump, err := httputil.DumpResponse(resp, true)
|
||||||
|
if err != nil {
|
||||||
|
return deviceResp{}, clues.Wrap(err, "dumping http response")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n-----\nresp %+v\n-----\n", string(respDump))
|
||||||
|
|
||||||
|
return deviceResp{}, clues.New("incorrect tenant or credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode/100 == 4 || resp.StatusCode/100 == 5 {
|
||||||
|
respDump, err := httputil.DumpResponse(resp, true)
|
||||||
|
if err != nil {
|
||||||
|
return deviceResp{}, clues.Wrap(err, "dumping http response")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n-----\nresp %+v\n-----\n", string(respDump))
|
||||||
|
|
||||||
|
return deviceResp{}, clues.New("non-2xx response: " + resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var ar deviceResp
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&ar)
|
||||||
|
|
||||||
|
return ar, clues.Wrap(err, "undecodable resp body").WithClues(ctx).OrNil()
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user