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

Remove dependency on github.com/microsoftgraph/msgraph-sdk-go #139

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ bootstrap:
fi

.PHONY: test
test: fmtcheck
test:
CGO_ENABLED=0 go test ./... $(TESTARGS) -timeout=20m

.PHONY: fmtcheck
Expand Down
210 changes: 170 additions & 40 deletions client/msgraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,83 @@
package client

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/google/uuid"
msgraphsdkgo "github.com/microsoftgraph/msgraph-sdk-go"
abs "github.com/microsoft/kiota-abstractions-go"
auth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication"
"github.com/microsoftgraph/msgraph-sdk-go/applications"
"github.com/microsoftgraph/msgraph-sdk-go/models"
)

type MSGraphApplication interface {
GetId() *string
GetPasswordCredentials() []Credentials // []Credentials
}

type Credentials interface {
GetKeyId() *uuid.UUID
GetSecretText() *string
GetEndDateTime() *time.Time
}

type APIApplicationValues struct {
Value []*APIApplication `json:"value"`
}

type APIApplication struct {
ID string `json:"id"`
PasswordCredentials []APICredentials `json:"passwordCredentials"`
}

func (a *APIApplication) GetId() *string {
return &a.ID
}

func (a *APIApplication) GetPasswordCredentials() []Credentials {
x := a.PasswordCredentials
y := []Credentials{}
for _, z := range x {
y = append(y, &z)
}
return y
}

type APICredentials struct {
KeyID *uuid.UUID `json:"keyId"`
SecretText *string `json:"secretText"`
EndDateTime *time.Time `json:"endDateTime"`
}

func (a *APICredentials) GetKeyId() *uuid.UUID {
return a.KeyID
}

func (a *APICredentials) GetSecretText() *string {
return a.SecretText
}

func (a *APICredentials) GetEndDateTime() *time.Time {
return a.EndDateTime
}

type MSGraphClient interface {
GetApplication(ctx context.Context, clientID string) (models.Applicationable, error)
AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime time.Time) (models.PasswordCredentialable, error)
GetApplication(ctx context.Context, clientID string) (MSGraphApplication, error)
AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime time.Time) (Credentials, error)
RemoveApplicationPassword(ctx context.Context, applicationObjectID string, keyID *uuid.UUID) error
}

var _ MSGraphClient = (*AppClient)(nil)

type AppClient struct {
client *msgraphsdkgo.GraphServiceClient
baseUrl string
authProvider *auth.AzureIdentityAuthenticationProvider
}

// NewMSGraphApplicationClient returns a new AppClient configured to interact with
Expand All @@ -41,63 +96,138 @@ func NewMSGraphApplicationClient(graphURI string, creds azcore.TokenCredential)
if err != nil {
return nil, err
}

adapter, err := msgraphsdkgo.NewGraphRequestAdapter(authProvider)
if err != nil {
return nil, err
}

adapter.SetBaseUrl(fmt.Sprintf("%s/v1.0", graphURI))
client := msgraphsdkgo.NewGraphServiceClient(adapter)

ac := &AppClient{
client: client,
baseUrl: fmt.Sprintf("%s/v1.0", graphURI),
authProvider: authProvider,
}

return ac, nil
}

func (c *AppClient) GetApplication(ctx context.Context, clientID string) (models.Applicationable, error) {
func (c *AppClient) GetApplication(ctx context.Context, clientID string) (MSGraphApplication, error) {
filter := fmt.Sprintf("appId eq '%s'", clientID)
req := applications.ApplicationsRequestBuilderGetRequestConfiguration{
QueryParameters: &applications.ApplicationsRequestBuilderGetQueryParameters{
Filter: &filter,
},
query := "$filter=" + url.QueryEscape(filter)
u, err := url.Parse(fmt.Sprintf("%s/applications?%s", c.baseUrl, query))
if err != nil {
return nil, err
}

resp, err := c.client.Applications().Get(ctx, &req)
req := abs.NewRequestInformation()
req.SetUri(*u)
err = c.authProvider.AuthenticateRequest(ctx, req, nil)
if err != nil {
return nil, err
}
headers := map[string][]string{
"accept": {"application/json"},
"authorization": req.Headers.Get("Authorization"),
}
request := &http.Request{
Method: "GET",
URL: u,
Header: headers,
}

apps := resp.GetValue()
if len(apps) == 0 {
resp, err := http.DefaultClient.Do(request)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! I rushed this out, but if we intended to go this route, I'd refactor this to use a common http client and DRY a few things.

But, it sounds like we're looking to use the msgraph SDK in more places, so merging this wouldn't really change anything. I'll close for now but keep the branch.

if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("error from msgraph: %v", resp.StatusCode)
}
all, err := io.ReadAll(resp.Body)
apps := APIApplicationValues{}
err = json.Unmarshal(all, &apps)
if err != nil {
return nil, err
}
if len(apps.Value) == 0 {
return nil, fmt.Errorf("no application found")
}
if len(apps) > 1 {
if len(apps.Value) > 1 {
return nil, fmt.Errorf("multiple applications found - double check your client_id")
}

return apps[0], nil
return apps.Value[0], nil
}

func (c *AppClient) AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime time.Time) (models.PasswordCredentialable, error) {
requestBody := applications.NewItemAddPasswordPostRequestBody()
passwordCredential := models.NewPasswordCredential()
passwordCredential.SetDisplayName(&displayName)
passwordCredential.SetEndDateTime(&endDateTime)
requestBody.SetPasswordCredential(passwordCredential)

resp, err := c.client.Applications().ByApplicationId(applicationObjectID).AddPassword().Post(ctx, requestBody, nil)
func (c *AppClient) AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime time.Time) (Credentials, error) {
bodyBytes, err := json.Marshal(map[string]interface{}{
"passwordCredential": map[string]interface{}{
"displayName": displayName,
"endDateTime": endDateTime,
},
})
if err != nil {
return nil, err
}
u, err := url.Parse(fmt.Sprintf("%s/applications/%s/addPassword", c.baseUrl, applicationObjectID))
if err != nil {
return nil, err
}
req := abs.NewRequestInformation()
req.SetUri(*u)
err = c.authProvider.AuthenticateRequest(ctx, req, nil)
if err != nil {
return nil, err
}
headers := map[string][]string{
"content-type": {"application/json"},
"authorization": req.Headers.Get("Authorization"),
}
request := &http.Request{
Method: "POST",
URL: u,
Header: headers,
Body: io.NopCloser(bytes.NewBuffer(bodyBytes)),
}

return resp, nil
resp, err := http.DefaultClient.Do(request)
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("error from msgraph: %v", resp.StatusCode)
}
all, err := io.ReadAll(resp.Body)
m := APICredentials{}
err = json.Unmarshal(all, &m)
if err != nil {
return nil, err
}
return &m, nil
}

func (c *AppClient) RemoveApplicationPassword(ctx context.Context, applicationObjectID string, keyID *uuid.UUID) error {
requestBody := applications.NewItemRemovePasswordPostRequestBody()
requestBody.SetKeyId(keyID)
bodyBytes, err := json.Marshal(map[string]interface{}{"keyId": keyID.String()})
if err != nil {
return err
}
u, err := url.Parse(fmt.Sprintf("%s/applications/%s/removePassword", c.baseUrl, applicationObjectID))
if err != nil {
return err
}
req := abs.NewRequestInformation()
req.SetUri(*u)
err = c.authProvider.AuthenticateRequest(ctx, req, nil)
if err != nil {
return err
}
headers := map[string][]string{
"content-type": {"application/json"},
"authorization": req.Headers.Get("Authorization"),
}
request := &http.Request{
Method: "POST",
URL: u,
Header: headers,
Body: io.NopCloser(bytes.NewBuffer(bodyBytes)),
}

return c.client.Applications().ByApplicationId(applicationObjectID).RemovePassword().Post(ctx, requestBody, nil)
resp, err := http.DefaultClient.Do(request)
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("error from msgraph: %v", resp.StatusCode)
}
return nil
}
11 changes: 1 addition & 10 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/vault/api v1.9.2
github.com/hashicorp/vault/sdk v0.9.2
github.com/microsoftgraph/msgraph-sdk-go v1.13.0
golang.org/x/oauth2 v0.11.0
)

Expand All @@ -25,7 +24,6 @@ require (
github.com/armon/go-radix v1.0.0 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cjlapao/common-go v0.0.39 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
Expand Down Expand Up @@ -54,12 +52,8 @@ require (
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/microsoft/kiota-abstractions-go v1.2.0 // indirect
github.com/microsoft/kiota-abstractions-go v1.2.0
github.com/microsoft/kiota-authentication-azure-go v1.0.0 // indirect
github.com/microsoft/kiota-http-go v1.1.0 // indirect
github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect
github.com/microsoft/kiota-serialization-json-go v1.0.4 // indirect
github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect
github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
Expand All @@ -70,10 +64,8 @@ require (
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pquerna/cachecontrol v0.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.opentelemetry.io/otel v1.16.0 // indirect
go.opentelemetry.io/otel/metric v1.16.0 // indirect
Expand All @@ -89,5 +81,4 @@ require (
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
15 changes: 0 additions & 15 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHo
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
Expand Down Expand Up @@ -164,16 +163,6 @@ github.com/microsoft/kiota-abstractions-go v1.2.0 h1:lUriJgqdCY/QajwWQOgTCQE9Aty
github.com/microsoft/kiota-abstractions-go v1.2.0/go.mod h1:RkxyZ5x87Njik7iVeQY9M2wtrrL1MJZcXiI/BxD/82g=
github.com/microsoft/kiota-authentication-azure-go v1.0.0 h1:29FNZZ/4nnCOwFcGWlB/sxPvWz487HA2bXH8jR5k2Rk=
github.com/microsoft/kiota-authentication-azure-go v1.0.0/go.mod h1:rnx3PRlkGdXDcA/0lZQTbBwyYGmc+3POt7HpE/e4jGw=
github.com/microsoft/kiota-http-go v1.1.0 h1:L5I93EiNtlP/X6YzeTlhjWt7Q1DxzC9CmWSVtX3b0tE=
github.com/microsoft/kiota-http-go v1.1.0/go.mod h1:zESUM6ovki9LEupqziCbxJ+FAYoF0dFDYZVpOkAfSLc=
github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI=
github.com/microsoft/kiota-serialization-form-go v1.0.0/go.mod h1:h4mQOO6KVTNciMF6azi1J9QB19ujSw3ULKcSNyXXOMA=
github.com/microsoft/kiota-serialization-json-go v1.0.4 h1:5TaISWwd2Me8clrK7SqNATo0tv9seOq59y4I5953egQ=
github.com/microsoft/kiota-serialization-json-go v1.0.4/go.mod h1:rM4+FsAY+9AEpBsBzkFFis+b/LZLlNKKewuLwK9Q6Mg=
github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA=
github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M=
github.com/microsoftgraph/msgraph-sdk-go v1.13.0 h1:k+3FJJYCSBcnIueefLrO4Ofsd4UP4NzOQ9k3La8I2oU=
github.com/microsoftgraph/msgraph-sdk-go v1.13.0/go.mod h1:ccLv84FJFtwdSzYWM/HlTes5FLzkzzBsYh9kg93/WS8=
github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0 h1:7NWTfyXvOjoizW7PmxNp3+8wCKPgpODs/D1cUZ3fkAY=
github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0/go.mod h1:tQb4q3YMIj2dWhhXhQSJ4ELpol931ANKzHSYK5kX1qE=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
Expand All @@ -194,7 +183,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
Expand All @@ -209,7 +197,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
Expand Down Expand Up @@ -240,7 +227,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
Expand Down Expand Up @@ -305,7 +291,6 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down