diff --git a/examples/DeviceCodeFlowSample.go b/examples/DeviceCodeFlowSample.go new file mode 100644 index 00000000..1ed0c0e7 --- /dev/null +++ b/examples/DeviceCodeFlowSample.go @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package main + +import ( + "context" + "fmt" + "time" + + msalgo "github.com/AzureAD/microsoft-authentication-library-for-go/src/msal" + log "github.com/sirupsen/logrus" +) + +func deviceCodeCallback(deviceCodeResult msalgo.IDeviceCodeResult) { + log.Infof(deviceCodeResult.GetMessage()) +} + +func setCancelTimeout(seconds int, cancelChannel chan bool) { + time.Sleep(time.Duration(seconds) * time.Second) + cancelChannel <- true +} + +func acquireTokenDeviceCode() { + cancelTimeout := 100 //Change this for cancel timeout + config := CreateConfig("config.json") + pcaParams := createPCAParams(config.GetClientID(), config.GetAuthority()) + publicClientApp, err := msalgo.CreatePublicClientApplication(pcaParams) + if err != nil { + log.Fatal(err) + } + cancelCtx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(cancelTimeout)*time.Second) + defer cancelFunc() + deviceCodeParams := msalgo.CreateAcquireTokenDeviceCodeParameters(cancelCtx, config.GetScopes(), deviceCodeCallback) + resultChannel := make(chan msalgo.IAuthenticationResult) + errChannel := make(chan error) + go func() { + result, err := publicClientApp.AcquireTokenByDeviceCode(deviceCodeParams) + errChannel <- err + resultChannel <- result + }() + err = <-errChannel + if err != nil { + log.Fatal(err) + } + result := <-resultChannel + fmt.Println("Access token is " + result.GetAccessToken()) +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..67e94407 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,18 @@ +# Running Examples for MSAL Go + +To run one of the examples of uses of MSAL Go, you need to first create a `config.json` file. The `config.json` file should look like the following: +```json +{ + "authority": "https://login.microsoftonline.com/organizations", + "client_id": "your_client_id", + "scopes": ["user.read"], + // You can find the other permission names from this document + // https://docs.microsoft.com/en-us/graph/permissions-reference + "username": "your_username", + "password": "your_password" //This is a sample only. DO NOT persist your password. +} +``` + +To run one of the examples, run the command `go run src/examples/*.go `. The example arguments are as follows: +* 1 - `DeviceCodeFlowSample.go` without cancellation timeout +* 2 - `UsernamePasswordPublicFlowSample.go` \ No newline at end of file diff --git a/examples/SampleUtils.go b/examples/SampleUtils.go new file mode 100644 index 00000000..33b8bda3 --- /dev/null +++ b/examples/SampleUtils.go @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package main + +import ( + "encoding/json" + "io/ioutil" + "os" + + msalgo "github.com/AzureAD/microsoft-authentication-library-for-go/src/msal" + log "github.com/sirupsen/logrus" +) + +//Config represents the config.json required to run the samples +type Config struct { + ClientID string `json:"client_id"` + Authority string `json:"authority"` + Scopes []string `json:"scopes"` + Username string `json:"username"` + Password string `json:"password"` +} + +//CreateConfig creates the Config struct from a json file +func CreateConfig(fileName string) *Config { + jsonFile, err := os.Open(fileName) + if err != nil { + log.Fatal(err) + } + defer jsonFile.Close() + data, err := ioutil.ReadAll(jsonFile) + config := &Config{} + err = json.Unmarshal(data, config) + if err != nil { + log.Fatal(err) + } + return config +} + +//GetClientID returns the Client ID of the config +func (c *Config) GetClientID() string { + return c.ClientID +} + +//GetAuthority returns the authority URI of the config +func (c *Config) GetAuthority() string { + return c.Authority +} + +//GetScopes returns all the scopes of the config +func (c *Config) GetScopes() []string { + return c.Scopes +} + +//GetUsername returns the username of the config +func (c *Config) GetUsername() string { + return c.Username +} + +//GetPassword returns the password of the config +func (c *Config) GetPassword() string { + return c.Password +} + +//createPCAParams is used to instantiate the parameters to create the Public Client Application +func createPCAParams(clientID string, authority string) *msalgo.PublicClientApplicationParameters { + pcaParams := msalgo.CreatePublicClientApplicationParameters(clientID) + pcaParams.SetAadAuthority(authority) + return pcaParams +} diff --git a/examples/main.go b/examples/main.go new file mode 100644 index 00000000..49a11449 --- /dev/null +++ b/examples/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" +) + +func main() { + exampleType := os.Args[1] + if exampleType == "1" { + acquireTokenDeviceCode() + } else if exampleType == "2" { + acquireByUsernamePasswordPublic() + } +} diff --git a/getpackages.cmd b/getpackages.cmd index 3d9bd7e9..7fd4eb8a 100644 --- a/getpackages.cmd +++ b/getpackages.cmd @@ -2,7 +2,7 @@ Rem getpackages Rem push ./src go get -u github.com/sirupsen/logrus -go get -u github.com/shirou/gopsutil/host +go get -u github.com/shirou/gopsutil@v1 go get -u github.com/twinj/uuid go get -u golang.org/x/crypto/ssh/terminal go get -u github.com/AzureAD\microsoft-authentication-library-for-go/src/internal/msalbase diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..f99f5bfe --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/AzureAD/microsoft-authentication-library-for-go + +go 1.14 + +require ( + github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect + github.com/go-ole/go-ole v1.2.4 // indirect + github.com/shirou/gopsutil v2.20.5+incompatible + github.com/sirupsen/logrus v1.6.0 + github.com/twinj/uuid v1.0.0 + golang.org/x/sys v0.0.0-20200610111108-226ff32320da // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..86896174 --- /dev/null +++ b/go.sum @@ -0,0 +1,19 @@ +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= +github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk= +github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38= +golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/src/AcquireTokenDeviceCodeParameters.go b/src/AcquireTokenDeviceCodeParameters.go deleted file mode 100644 index fa06643e..00000000 --- a/src/AcquireTokenDeviceCodeParameters.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package msalgo - -import "internal/msalbase" - -// AcquireTokenDeviceCodeParameters stuff -type AcquireTokenDeviceCodeParameters struct { - commonParameters *acquireTokenCommonParameters -} - -// CreateAcquireTokenDeviceCodeParameters stuff -func CreateAcquireTokenDeviceCodeParameters(scopes []string) *AcquireTokenDeviceCodeParameters { - p := &AcquireTokenDeviceCodeParameters{ - commonParameters: createAcquireTokenCommonParameters(scopes), - } - return p -} - -func (p *AcquireTokenDeviceCodeParameters) augmentAuthenticationParameters(authParams *msalbase.AuthParametersInternal) { - p.commonParameters.augmentAuthenticationParameters(authParams) - authParams.SetAuthorizationType(msalbase.AuthorizationTypeDeviceCode) -} diff --git a/src/examples/msalconsole/main.go b/src/examples/msalconsole/main.go deleted file mode 100644 index 65d23695..00000000 --- a/src/examples/msalconsole/main.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package main - -import ( - "bufio" - "fmt" - "os" - "strings" - "syscall" - - log "github.com/sirupsen/logrus" - - "github.com/AzureAD/microsoft-authentication-library-for-go/src" - "github.com/shirou/gopsutil/host" - "golang.org/x/crypto/ssh/terminal" -) - -func createParams() *msalgo.PublicClientApplicationParameters { - pcaParameters := msalgo.CreatePublicClientApplicationParameters("0615b6ca-88d4-4884-8729-b178178f7c27") - pcaParameters.SetAadAuthority("https://login.microsoftonline.com/organizations") - // pcaParameters.SetHttpClient() - return pcaParameters -} - -func acquireByDeviceCode() { - pca, err := msalgo.CreatePublicClientApplication(createParams()) - if err != nil { - log.Fatal(err) - } - - log.Info("acquiring token by device code") - deviceCodeParams := msalgo.CreateAcquireTokenDeviceCodeParameters([]string{"user.read"}) - result, err := pca.AcquireTokenByDeviceCode(deviceCodeParams) - if err != nil { - log.Fatal(err) - } - log.Info("ACCESS TOKEN: " + result.GetAccessToken()) -} - -func readInput() string { - reader := bufio.NewReader(os.Stdin) - value, _ := reader.ReadString('\n') - value = strings.TrimSpace(value) - return value -} - -func readMaskedInput() string { - bytes, _ := terminal.ReadPassword(int(syscall.Stdin)) - value := string(bytes) - value = strings.TrimSpace(value) - return value -} - -func acquireByUsernamePassword() { - - pca, err := msalgo.CreatePublicClientApplication(createParams()) - if err != nil { - log.Fatal(err) - } - - log.Info("acquiring token by username password") - - fmt.Println() - fmt.Print("Enter username: ") - userName := readInput() - fmt.Print("Enter password: ") - password := readMaskedInput() - fmt.Println() - fmt.Println() - - userNameParams := msalgo.CreateAcquireTokenUsernamePasswordParameters([]string{"user.read"}, userName, password) - result, err := pca.AcquireTokenByUsernamePassword(userNameParams) - if err != nil { - log.Fatal(err) - } - log.Info("ACCESS TOKEN: " + result.GetAccessToken()) -} - -func main() { - - h, _ := host.Info() - log.Infof("%#v", h) - - // set this to get function names in the logs: log.SetReportCaller(true) - log.Info("creating pca") - - acquireByDeviceCode() - //acquireByUsernamePassword() -} diff --git a/src/internal/msalbase/DeviceCodeResult.go b/src/internal/msalbase/DeviceCodeResult.go index 16be71ab..458d3a0f 100644 --- a/src/internal/msalbase/DeviceCodeResult.go +++ b/src/internal/msalbase/DeviceCodeResult.go @@ -8,6 +8,7 @@ import ( "time" ) +//DeviceCodeResult stores the response from the STS device code endpoint type DeviceCodeResult struct { userCode string deviceCode string @@ -19,43 +20,52 @@ type DeviceCodeResult struct { scopes []string } +//CreateDeviceCodeResult creates a DeviceCodeResult instance func CreateDeviceCodeResult(userCode string, deviceCode string, verificationURL string, expiresOn time.Time, interval int, message string, clientID string, scopes []string) *DeviceCodeResult { return &DeviceCodeResult{userCode, deviceCode, verificationURL, expiresOn, interval, message, clientID, scopes} } -func (r DeviceCodeResult) String() string { - return fmt.Sprintf("UserCode: (%v)\nDeviceCode: (%v)\nURL: (%v)\nMessage: (%v)\n", r.GetUserCode(), r.GetDeviceCode(), r.GetVerificationURL(), r.GetMessage()) +func (dcr DeviceCodeResult) String() string { + return fmt.Sprintf("UserCode: (%v)\nDeviceCode: (%v)\nURL: (%v)\nMessage: (%v)\n", dcr.GetUserCode(), dcr.GetDeviceCode(), dcr.GetVerificationURL(), dcr.GetMessage()) } +//GetUserCode returns the code the user needs to provide when authentication at the verification URI func (dcr *DeviceCodeResult) GetUserCode() string { return dcr.userCode } +//GetDeviceCode returns the code used in the access token request func (dcr *DeviceCodeResult) GetDeviceCode() string { return dcr.deviceCode } +//GetVerificationURL returns the URL where user can authenticate func (dcr *DeviceCodeResult) GetVerificationURL() string { return dcr.verificationURL } +//GetExpiresOn returns the expiration time of device code in seconds func (dcr *DeviceCodeResult) GetExpiresOn() time.Time { return dcr.expiresOn } +//GetInterval returns the interval at which the STS should be polled at func (dcr *DeviceCodeResult) GetInterval() int { return dcr.interval } +//GetMessage returns the message which should be displayed to the user func (dcr *DeviceCodeResult) GetMessage() string { return dcr.message } +//GetClientID returns the UUID issued by the authorization server for your application func (dcr *DeviceCodeResult) GetClientID() string { return dcr.clientID } +//GetScopes returns the scopes used to request access a protected API func (dcr *DeviceCodeResult) GetScopes() []string { return dcr.scopes } diff --git a/src/internal/msalbase/TokenResponse.go b/src/internal/msalbase/TokenResponse.go index ec788ce6..329a96cb 100644 --- a/src/internal/msalbase/TokenResponse.go +++ b/src/internal/msalbase/TokenResponse.go @@ -53,6 +53,10 @@ func (tr *TokenResponse) IsAuthorizationPending() bool { return tr.baseResponse.Error == "authorization_pending" } +func (tr *TokenResponse) IsSlowDown() bool { + return tr.baseResponse.Error == "slow_down" +} + func (tr *TokenResponse) GetAccessToken() string { return tr.accessToken } diff --git a/src/internal/requests/AadInstanceDiscovery.go b/src/internal/requests/AadInstanceDiscovery.go index 0105ad39..19e3cb07 100644 --- a/src/internal/requests/AadInstanceDiscovery.go +++ b/src/internal/requests/AadInstanceDiscovery.go @@ -6,7 +6,7 @@ package requests import ( "sync" - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) var instanceDiscoveryCache = map[string]*instanceDiscoveryMetadata{} diff --git a/src/internal/requests/AuthorityEndpointResolutionManager.go b/src/internal/requests/AuthorityEndpointResolutionManager.go index f7689e71..08f1d25c 100644 --- a/src/internal/requests/AuthorityEndpointResolutionManager.go +++ b/src/internal/requests/AuthorityEndpointResolutionManager.go @@ -9,7 +9,7 @@ import ( log "github.com/sirupsen/logrus" - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) type authorityEndpointCacheEntry struct { diff --git a/src/internal/requests/DeviceCodeRequest.go b/src/internal/requests/DeviceCodeRequest.go deleted file mode 100644 index 64426b58..00000000 --- a/src/internal/requests/DeviceCodeRequest.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package requests - -import ( - "errors" - "time" - - log "github.com/sirupsen/logrus" - - "internal/msalbase" -) - -// DeviceCodeRequest stuff -type DeviceCodeRequest struct { - webRequestManager IWebRequestManager - cacheManager msalbase.ICacheManager - authParameters *msalbase.AuthParametersInternal -} - -// CreateDeviceCodeRequest stuff -func CreateDeviceCodeRequest( - webRequestManager IWebRequestManager, - cacheManager msalbase.ICacheManager, - authParameters *msalbase.AuthParametersInternal) *DeviceCodeRequest { - req := &DeviceCodeRequest{webRequestManager, cacheManager, authParameters} - return req -} - -// Execute stuff -func (req *DeviceCodeRequest) Execute() (*msalbase.TokenResponse, error) { - - // resolve authority endpoints - - deviceCodeResult, err := req.webRequestManager.GetDeviceCodeResult(req.authParameters) - if err != nil { - return nil, err - } - - // fire deviceCodeResult up to user - log.Infof("%v", deviceCodeResult) - - return req.waitForTokenResponse(deviceCodeResult) -} - -func (req *DeviceCodeRequest) waitForTokenResponse(deviceCodeResult *msalbase.DeviceCodeResult) (*msalbase.TokenResponse, error) { - - timeRemaining := deviceCodeResult.GetExpiresOn().Sub(time.Now().UTC()) - - for timeRemaining.Seconds() > 0.0 { - // todo: how to check for cancellation requested... - - // todo: learn more about go error handling so that this is managed through error flow and not parsing the token response... - - tokenResponse, err := req.webRequestManager.GetAccessTokenFromDeviceCodeResult(req.authParameters, deviceCodeResult) - if err != nil { - if isErrorAuthorizationPending(err) { - timeRemaining = deviceCodeResult.GetExpiresOn().Sub(time.Now().UTC()) - } else { - return nil, err - } - } else { - if tokenResponse.IsAuthorizationPending() { - timeRemaining = deviceCodeResult.GetExpiresOn().Sub(time.Now().UTC()) - } else { - return tokenResponse, nil - } - } - - time.Sleep(5 * time.Second) - } - - return nil, errors.New("Verification code expired before contacting the server") -} diff --git a/src/internal/requests/DeviceCodeResponse.go b/src/internal/requests/DeviceCodeResponse.go index 1c0d64c5..347b2f93 100644 --- a/src/internal/requests/DeviceCodeResponse.go +++ b/src/internal/requests/DeviceCodeResponse.go @@ -5,10 +5,9 @@ package requests import ( "encoding/json" - "strconv" "time" - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) type deviceCodeResponse struct { @@ -16,12 +15,9 @@ type deviceCodeResponse struct { UserCode string `json:"user_code"` DeviceCode string `json:"device_code"` VerificationURL string `json:"verification_url"` - ExpiresInStr string `json:"expires_in"` - IntervalStr string `json:"interval"` + ExpiresIn int `json:"expires_in"` + Interval int `json:"interval"` Message string `json:"message"` - - ExpiresIn int - Interval int } // createDeviceCodeResponse stuff @@ -39,18 +35,6 @@ func createDeviceCodeResponse(responseCode int, responseData string) (*deviceCod dcResponse.BaseResponse = baseResponse - expiresIn, err := strconv.Atoi(dcResponse.ExpiresInStr) - if err != nil { - return nil, err - } - dcResponse.ExpiresIn = expiresIn - - interval, err := strconv.Atoi(dcResponse.IntervalStr) - if err != nil { - return nil, err - } - dcResponse.Interval = interval - return dcResponse, nil } diff --git a/src/internal/requests/IRealmMetadata.go b/src/internal/requests/IRealmMetadata.go index d77c4219..eef219cb 100644 --- a/src/internal/requests/IRealmMetadata.go +++ b/src/internal/requests/IRealmMetadata.go @@ -4,7 +4,7 @@ package requests import ( - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) type IRealmMetadata interface { diff --git a/src/internal/requests/ITokenRequester.go b/src/internal/requests/ITokenRequester.go index d8e9c1b0..dd71d31a 100644 --- a/src/internal/requests/ITokenRequester.go +++ b/src/internal/requests/ITokenRequester.go @@ -3,7 +3,7 @@ package requests -import "internal/msalbase" +import "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" type ITokenRequester interface { Execute() (*msalbase.TokenResponse, error) diff --git a/src/internal/requests/IWebRequestManager.go b/src/internal/requests/IWebRequestManager.go index fa373127..51f60fe9 100644 --- a/src/internal/requests/IWebRequestManager.go +++ b/src/internal/requests/IWebRequestManager.go @@ -4,8 +4,8 @@ package requests import ( - "internal/msalbase" - "internal/wstrust" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/wstrust" ) // IWebRequestManager interface diff --git a/src/internal/requests/InteractiveRequest.go b/src/internal/requests/InteractiveRequest.go index 5f0cde2d..0e2fdda3 100644 --- a/src/internal/requests/InteractiveRequest.go +++ b/src/internal/requests/InteractiveRequest.go @@ -4,7 +4,7 @@ package requests import ( - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) // InteractiveRequest stuff diff --git a/src/internal/requests/NetworkedCacheManager.go b/src/internal/requests/NetworkedCacheManager.go index 588d7690..3abfec33 100644 --- a/src/internal/requests/NetworkedCacheManager.go +++ b/src/internal/requests/NetworkedCacheManager.go @@ -4,7 +4,7 @@ package requests import ( - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) type NetworkedCacheManager struct { diff --git a/src/internal/requests/OpenIdConfigurationEndpointManager.go b/src/internal/requests/OpenIdConfigurationEndpointManager.go index 7b3a52ba..756374da 100644 --- a/src/internal/requests/OpenIdConfigurationEndpointManager.go +++ b/src/internal/requests/OpenIdConfigurationEndpointManager.go @@ -6,7 +6,7 @@ package requests import ( "errors" - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) type IOpenIdConfigurationEndpointManager interface { diff --git a/src/internal/requests/RealmMetadata.go b/src/internal/requests/RealmMetadata.go index e0a4a54b..af4f367a 100644 --- a/src/internal/requests/RealmMetadata.go +++ b/src/internal/requests/RealmMetadata.go @@ -4,7 +4,7 @@ package requests import ( - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) type RealmMetadata struct { diff --git a/src/internal/requests/RefreshTokenExchangeRequest.go b/src/internal/requests/RefreshTokenExchangeRequest.go index 29375583..c4d54983 100644 --- a/src/internal/requests/RefreshTokenExchangeRequest.go +++ b/src/internal/requests/RefreshTokenExchangeRequest.go @@ -4,7 +4,7 @@ package requests import ( - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) // RefreshTokenExchangeRequest stuff diff --git a/src/internal/requests/TenantDiscoveryResponse.go b/src/internal/requests/TenantDiscoveryResponse.go index dce076b4..7c27a758 100644 --- a/src/internal/requests/TenantDiscoveryResponse.go +++ b/src/internal/requests/TenantDiscoveryResponse.go @@ -6,7 +6,7 @@ package requests import ( "encoding/json" - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) // TenantDiscoveryResponse stuff diff --git a/src/internal/requests/UsernamePasswordRequest.go b/src/internal/requests/UsernamePasswordRequest.go index b958746c..b6e06c3a 100644 --- a/src/internal/requests/UsernamePasswordRequest.go +++ b/src/internal/requests/UsernamePasswordRequest.go @@ -8,7 +8,7 @@ import ( log "github.com/sirupsen/logrus" - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) // UsernamePasswordRequest stuff diff --git a/src/internal/requests/WebRequestManager.go b/src/internal/requests/WebRequestManager.go index bf65098d..b620b7a2 100644 --- a/src/internal/requests/WebRequestManager.go +++ b/src/internal/requests/WebRequestManager.go @@ -15,8 +15,9 @@ import ( log "github.com/sirupsen/logrus" - "internal/msalbase" - "internal/wstrust" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" + + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/wstrust" ) // WebRequestManager stuff @@ -24,9 +25,12 @@ type WebRequestManager struct { httpManager *msalbase.HTTPManager } -func isErrorAuthorizationPending(err error) bool { - // todo: implement me! - return false +func IsErrorAuthorizationPending(err error) bool { + return err.Error() == "authorization_pending" +} + +func IsErrorSlowDown(err error) bool { + return err.Error() == "slow_down" } // ContentType stuff @@ -218,8 +222,7 @@ func joinScopes(scopes []string) string { func addScopeQueryParam(queryParams map[string]string, authParameters *msalbase.AuthParametersInternal) { log.Trace("Adding scopes 'openid', 'offline_access', 'profile'") requestedScopes := authParameters.GetScopes() - - // openid equired to get an id token + // openid required to get an id token // offline_access required to get a refresh token // profile required to get the client_info field back requestedScopes = append(requestedScopes, "openid", "offline_access", "profile") diff --git a/src/internal/tokencache/CacheManager.go b/src/internal/tokencache/CacheManager.go index 9214f24f..ce8f65ab 100644 --- a/src/internal/tokencache/CacheManager.go +++ b/src/internal/tokencache/CacheManager.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" log "github.com/sirupsen/logrus" ) diff --git a/src/internal/tokencache/IStorageManager.go b/src/internal/tokencache/IStorageManager.go index 3e6f5b3c..f8764647 100644 --- a/src/internal/tokencache/IStorageManager.go +++ b/src/internal/tokencache/IStorageManager.go @@ -4,7 +4,7 @@ package tokencache import ( - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) type OperationStatusType int diff --git a/src/internal/tokencache/StorageManager_Windows.go b/src/internal/tokencache/StorageManager_Windows.go index dc3fbdb7..fe5a9aba 100644 --- a/src/internal/tokencache/StorageManager_Windows.go +++ b/src/internal/tokencache/StorageManager_Windows.go @@ -8,7 +8,7 @@ package tokencache import ( "errors" - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" ) type windowsStorageManager struct { diff --git a/src/internal/wstrust/WsTrustEndpoint.go b/src/internal/wstrust/WsTrustEndpoint.go index 279e3f07..54d0d903 100644 --- a/src/internal/wstrust/WsTrustEndpoint.go +++ b/src/internal/wstrust/WsTrustEndpoint.go @@ -10,7 +10,7 @@ import ( log "github.com/sirupsen/logrus" // "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" - "internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" uuid "github.com/twinj/uuid" ) diff --git a/src/AcquireTokenCommonParameters.go b/src/msal/AcquireTokenCommonParameters.go similarity index 83% rename from src/AcquireTokenCommonParameters.go rename to src/msal/AcquireTokenCommonParameters.go index 2e7e8eaa..3fa0dde7 100644 --- a/src/AcquireTokenCommonParameters.go +++ b/src/msal/AcquireTokenCommonParameters.go @@ -3,7 +3,7 @@ package msalgo -import "internal/msalbase" +import "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" type acquireTokenCommonParameters struct { scopes []string diff --git a/src/msal/AcquireTokenDeviceCodeParameters.go b/src/msal/AcquireTokenDeviceCodeParameters.go new file mode 100644 index 00000000..cf62f836 --- /dev/null +++ b/src/msal/AcquireTokenDeviceCodeParameters.go @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package msalgo + +import ( + "context" + + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" +) + +// AcquireTokenDeviceCodeParameters stuff +type AcquireTokenDeviceCodeParameters struct { + commonParameters *acquireTokenCommonParameters + deviceCodeCallback func(IDeviceCodeResult) + cancelCtx context.Context +} + +// CreateAcquireTokenDeviceCodeParameters stuff +func CreateAcquireTokenDeviceCodeParameters(cancelCtx context.Context, scopes []string, + deviceCodeCallback func(IDeviceCodeResult)) *AcquireTokenDeviceCodeParameters { + p := &AcquireTokenDeviceCodeParameters{ + commonParameters: createAcquireTokenCommonParameters(scopes), + deviceCodeCallback: deviceCodeCallback, + cancelCtx: cancelCtx, + } + return p +} + +func (p *AcquireTokenDeviceCodeParameters) augmentAuthenticationParameters(authParams *msalbase.AuthParametersInternal) { + p.commonParameters.augmentAuthenticationParameters(authParams) + authParams.SetAuthorizationType(msalbase.AuthorizationTypeDeviceCode) +} + +func (p *AcquireTokenDeviceCodeParameters) GetCancelContext() context.Context { + return p.cancelCtx +} diff --git a/src/AcquireTokenInteractiveParameters.go b/src/msal/AcquireTokenInteractiveParameters.go similarity index 89% rename from src/AcquireTokenInteractiveParameters.go rename to src/msal/AcquireTokenInteractiveParameters.go index ccb4175e..e7c4a1f9 100644 --- a/src/AcquireTokenInteractiveParameters.go +++ b/src/msal/AcquireTokenInteractiveParameters.go @@ -3,7 +3,7 @@ package msalgo -import "internal/msalbase" +import "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" // AcquireTokenInteractiveParameters stuff type AcquireTokenInteractiveParameters struct { diff --git a/src/AcquireTokenSilentParameters.go b/src/msal/AcquireTokenSilentParameters.go similarity index 78% rename from src/AcquireTokenSilentParameters.go rename to src/msal/AcquireTokenSilentParameters.go index d62cf38f..57c38acb 100644 --- a/src/AcquireTokenSilentParameters.go +++ b/src/msal/AcquireTokenSilentParameters.go @@ -3,7 +3,7 @@ package msalgo -import "internal/msalbase" +import "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" // AcquireTokenSilentParameters stuff type AcquireTokenSilentParameters struct { @@ -11,7 +11,7 @@ type AcquireTokenSilentParameters struct { } // CreateAcquireTokenSilentParameters stuff -func CreateAcquireTokenSilentParameters(scopes []string, username string, password string) *AcquireTokenSilentParameters { +func CreateAcquireTokenSilentParameters(scopes []string) *AcquireTokenSilentParameters { p := &AcquireTokenSilentParameters{ commonParameters: createAcquireTokenCommonParameters(scopes), } diff --git a/src/AcquireTokenUsernamePasswordParameters.go b/src/msal/AcquireTokenUsernamePasswordParameters.go similarity index 94% rename from src/AcquireTokenUsernamePasswordParameters.go rename to src/msal/AcquireTokenUsernamePasswordParameters.go index 60935bf2..451eecaa 100644 --- a/src/AcquireTokenUsernamePasswordParameters.go +++ b/src/msal/AcquireTokenUsernamePasswordParameters.go @@ -3,7 +3,7 @@ package msalgo -import "internal/msalbase" +import "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" // AcquireTokenUsernamePasswordParameters stuff type AcquireTokenUsernamePasswordParameters struct { diff --git a/src/ApplicationCommonParameters.go b/src/msal/ApplicationCommonParameters.go similarity index 92% rename from src/ApplicationCommonParameters.go rename to src/msal/ApplicationCommonParameters.go index 2b0cd5de..e82fbc13 100644 --- a/src/ApplicationCommonParameters.go +++ b/src/msal/ApplicationCommonParameters.go @@ -3,7 +3,7 @@ package msalgo -import "internal/msalbase" +import "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" type applicationCommonParameters struct { clientID string diff --git a/src/msal/DeviceCodeRequest.go b/src/msal/DeviceCodeRequest.go new file mode 100644 index 00000000..0985ad47 --- /dev/null +++ b/src/msal/DeviceCodeRequest.go @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package msalgo + +import ( + "context" + "errors" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/requests" +) + +// DeviceCodeRequest stuff +type deviceCodeRequest struct { + webRequestManager requests.IWebRequestManager + cacheManager msalbase.ICacheManager + authParameters *msalbase.AuthParametersInternal + deviceCodeCallback func(IDeviceCodeResult) + cancelCtx context.Context +} + +// CreateDeviceCodeRequest stuff +func createDeviceCodeRequest(cancelCtx context.Context, + webRequestManager requests.IWebRequestManager, + cacheManager msalbase.ICacheManager, + authParameters *msalbase.AuthParametersInternal, + deviceCodeCallback func(IDeviceCodeResult)) *deviceCodeRequest { + req := &deviceCodeRequest{webRequestManager, cacheManager, authParameters, deviceCodeCallback, cancelCtx} + return req +} + +// Execute stuff +func (req *deviceCodeRequest) Execute() (*msalbase.TokenResponse, error) { + // resolve authority endpoints + resolutionManager := requests.CreateAuthorityEndpointResolutionManager(req.webRequestManager) + endpoints, err := resolutionManager.ResolveEndpoints(req.authParameters.GetAuthorityInfo(), "") + if err != nil { + return nil, err + } + + req.authParameters.SetAuthorityEndpoints(endpoints) + deviceCodeResult, err := req.webRequestManager.GetDeviceCodeResult(req.authParameters) + if err != nil { + return nil, err + } + // fire deviceCodeResult up to user + log.Infof("%v", deviceCodeResult) + req.deviceCodeCallback(deviceCodeResult) + return req.waitForTokenResponse(deviceCodeResult) +} + +func (req *deviceCodeRequest) waitForTokenResponse(deviceCodeResult *msalbase.DeviceCodeResult) (*msalbase.TokenResponse, error) { + + interval := deviceCodeResult.GetInterval() + timeRemaining := deviceCodeResult.GetExpiresOn().Sub(time.Now().UTC()) + + for timeRemaining.Seconds() > 0.0 { + select { + case <-req.cancelCtx.Done(): + return nil, errors.New("Token request canceled") + default: + tokenResponse, err := req.webRequestManager.GetAccessTokenFromDeviceCodeResult(req.authParameters, deviceCodeResult) + if err != nil { + if requests.IsErrorAuthorizationPending(err) { + timeRemaining = deviceCodeResult.GetExpiresOn().Sub(time.Now().UTC()) + } else if requests.IsErrorSlowDown(err) { + interval += 5 + } else { + return nil, err + } + } else { + return tokenResponse, nil + } + + time.Sleep(time.Duration(interval) * time.Second) + } + } + + return nil, errors.New("Verification code expired before contacting the server") +} diff --git a/src/IAccount.go b/src/msal/IAccount.go similarity index 100% rename from src/IAccount.go rename to src/msal/IAccount.go diff --git a/src/IAuthenticationResult.go b/src/msal/IAuthenticationResult.go similarity index 100% rename from src/IAuthenticationResult.go rename to src/msal/IAuthenticationResult.go diff --git a/src/msal/IClientApplication.go b/src/msal/IClientApplication.go new file mode 100644 index 00000000..40ef2d5c --- /dev/null +++ b/src/msal/IClientApplication.go @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package msalgo + +import ( + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/requests" +) + +type IClientApplication interface { + AcquireTokenSilent(*AcquireTokenSilentParameters) (IAuthenticationResult, error) + executeTokenRequestWithoutCacheWrite(requests.ITokenRequester, *msalbase.AuthParametersInternal) (IAuthenticationResult, error) +} diff --git a/src/msal/IDeviceCodeResult.go b/src/msal/IDeviceCodeResult.go new file mode 100644 index 00000000..9cca355e --- /dev/null +++ b/src/msal/IDeviceCodeResult.go @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package msalgo + +import "time" + +// IDeviceCodeResult is an interface for DeviceCodeResult +type IDeviceCodeResult interface { + GetMessage() string + String() string + GetUserCode() string + GetDeviceCode() string + GetVerificationURL() string + GetExpiresOn() time.Time + GetInterval() int +} diff --git a/src/PublicClientApplication.go b/src/msal/PublicClientApplication.go similarity index 85% rename from src/PublicClientApplication.go rename to src/msal/PublicClientApplication.go index e5fad206..03be4c21 100644 --- a/src/PublicClientApplication.go +++ b/src/msal/PublicClientApplication.go @@ -4,9 +4,9 @@ package msalgo import ( - "internal/msalbase" - "internal/requests" - "internal/tokencache" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/requests" + "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/tokencache" ) // PublicClientApplication is used to acquire tokens in desktop or mobile applications (Desktop / UWP / Xamarin.iOS / Xamarin.Android). @@ -18,7 +18,7 @@ type PublicClientApplication struct { cacheManager msalbase.ICacheManager } -// CreatePublicClientApplication stuff +// CreatePublicClientApplication creates a PublicClientApplication Instance given its parameters, which include client ID and authority info func CreatePublicClientApplication(pcaParameters *PublicClientApplicationParameters) (*PublicClientApplication, error) { err := pcaParameters.validate() if err != nil { @@ -70,19 +70,17 @@ func (pca *PublicClientApplication) AcquireTokenByDeviceCode( deviceCodeParameters *AcquireTokenDeviceCodeParameters) (IAuthenticationResult, error) { authParams := pca.pcaParameters.createAuthenticationParameters() deviceCodeParameters.augmentAuthenticationParameters(authParams) - - req := requests.CreateDeviceCodeRequest(pca.webRequestManager, pca.cacheManager, authParams) + req := createDeviceCodeRequest(deviceCodeParameters.GetCancelContext(), pca.webRequestManager, pca.cacheManager, authParams, deviceCodeParameters.deviceCodeCallback) return pca.executeTokenRequestWithoutCacheWrite(req, authParams) } // AcquireTokenInteractive stuff func (pca *PublicClientApplication) AcquireTokenInteractive( - interactiveParams *AcquireTokenInteractiveParameters) (IAuthenticationResult, error) { + interactiveParams *AcquireTokenInteractiveParameters, code string) (IAuthenticationResult, error) { authParams := pca.pcaParameters.createAuthenticationParameters() interactiveParams.augmentAuthenticationParameters(authParams) - req := requests.CreateInteractiveRequest(pca.webRequestManager, pca.cacheManager, authParams) - return pca.executeTokenRequestWithCacheWrite(req, authParams) + return pca.executeTokenRequestWithoutCacheWrite(req, authParams) } // executeTokenRequestWithoutCacheWrite stuff diff --git a/src/PublicClientApplicationParameters.go b/src/msal/PublicClientApplicationParameters.go similarity index 91% rename from src/PublicClientApplicationParameters.go rename to src/msal/PublicClientApplicationParameters.go index e3f7dc7f..b04ae1e0 100644 --- a/src/PublicClientApplicationParameters.go +++ b/src/msal/PublicClientApplicationParameters.go @@ -3,7 +3,7 @@ package msalgo -import "internal/msalbase" +import "github.com/AzureAD/microsoft-authentication-library-for-go/src/internal/msalbase" // PublicClientApplicationParameters stuff type PublicClientApplicationParameters struct { diff --git a/src/msal.go b/src/msal/msal.go similarity index 100% rename from src/msal.go rename to src/msal/msal.go