Add Azure Provider
This commit is contained in:
		
							parent
							
								
									d5a332c3f2
								
							
						
					
					
						commit
						10f47e325b
					
				
							
								
								
									
										19
									
								
								README.md
								
								
								
								
							
							
						
						
									
										19
									
								
								README.md
								
								
								
								
							|  | @ -29,6 +29,8 @@ You will need to register an OAuth application with a Provider (Google, Github o | ||||||
| Valid providers are : | Valid providers are : | ||||||
| 
 | 
 | ||||||
| * [Google](#google-auth-provider) *default* | * [Google](#google-auth-provider) *default* | ||||||
|  | 
 | ||||||
|  | * [Azure](#azure-auth-provider) | ||||||
| * [GitHub](#github-auth-provider) | * [GitHub](#github-auth-provider) | ||||||
| * [LinkedIn](#linkedin-auth-provider) | * [LinkedIn](#linkedin-auth-provider) | ||||||
| * [MyUSA](#myusa-auth-provider) | * [MyUSA](#myusa-auth-provider) | ||||||
|  | @ -76,6 +78,15 @@ and the user will be checked against all the provided groups. | ||||||
| 
 | 
 | ||||||
| Note: The user is checked against the group members list on initial authentication and every time the token is refreshed ( about once an hour ). | Note: The user is checked against the group members list on initial authentication and every time the token is refreshed ( about once an hour ). | ||||||
| 
 | 
 | ||||||
|  | ### Azure Auth Provider | ||||||
|  | 
 | ||||||
|  | 1. [Add an application](https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/) to your Azure Active Directory tenant. | ||||||
|  | 2. On the App properties page provide the correct Sign-On URL ie `https//internal.yourcompany.com/oauth2/callback` | ||||||
|  | 3. If applicable take note of your `TenantID` and provide it via the `--azure-tenant=<YOUR TENANT ID>` commandline option. Default the `common` tenant is used. | ||||||
|  | 
 | ||||||
|  | The Azure AD auth provider uses `openid` as it default scope. It uses `https://graph.windows.net` as a default protected resource. It call to `https://graph.windows.net/me` to get the email address of the user that logs in. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| ### GitHub Auth Provider | ### GitHub Auth Provider | ||||||
| 
 | 
 | ||||||
| 1. Create a new project: https://github.com/settings/developers | 1. Create a new project: https://github.com/settings/developers | ||||||
|  | @ -102,6 +113,12 @@ For LinkedIn, the registration steps are: | ||||||
| 
 | 
 | ||||||
| The [MyUSA](https://alpha.my.usa.gov) authentication service ([GitHub](https://github.com/18F/myusa)) | The [MyUSA](https://alpha.my.usa.gov) authentication service ([GitHub](https://github.com/18F/myusa)) | ||||||
| 
 | 
 | ||||||
|  | ### Microsoft Azure AD Provider | ||||||
|  | 
 | ||||||
|  | For adding an application to the Microsoft Azure AD follow [these steps to add an application](https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/). | ||||||
|  | 
 | ||||||
|  | Take note of your `TenantId` if applicable for your situation. The `TenantId` can be used to override the default `common` authorization server with a tenant specific server. | ||||||
|  | 
 | ||||||
| ## Email Authentication | ## Email Authentication | ||||||
| 
 | 
 | ||||||
| To authorize by email domain use `--email-domain=yourcompany.com`. To authorize individual email addresses use `--authenticated-emails-file=/path/to/file` with one email per line. To authorize all email addresse use `--email-domain=*`. | To authorize by email domain use `--email-domain=yourcompany.com`. To authorize individual email addresses use `--authenticated-emails-file=/path/to/file` with one email per line. To authorize all email addresse use `--email-domain=*`. | ||||||
|  | @ -120,6 +137,7 @@ An example [oauth2_proxy.cfg](contrib/oauth2_proxy.cfg.example) config file is i | ||||||
| Usage of oauth2_proxy: | Usage of oauth2_proxy: | ||||||
|   -approval-prompt="force": Oauth approval_prompt |   -approval-prompt="force": Oauth approval_prompt | ||||||
|   -authenticated-emails-file="": authenticate against emails via file (one per line) |   -authenticated-emails-file="": authenticate against emails via file (one per line) | ||||||
|  |   -azure-tenant="common": go to a tenant-specific or common (tenant-independent) endpoint. | ||||||
|   -basic-auth-password="": the password to set when passing the HTTP Basic Auth header |   -basic-auth-password="": the password to set when passing the HTTP Basic Auth header | ||||||
|   -client-id="": the OAuth Client ID: ie: "123456.apps.googleusercontent.com" |   -client-id="": the OAuth Client ID: ie: "123456.apps.googleusercontent.com" | ||||||
|   -client-secret="": the OAuth Client Secret |   -client-secret="": the OAuth Client Secret | ||||||
|  | @ -151,6 +169,7 @@ Usage of oauth2_proxy: | ||||||
|   -proxy-prefix="/oauth2": the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in) |   -proxy-prefix="/oauth2": the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in) | ||||||
|   -redeem-url="": Token redemption endpoint |   -redeem-url="": Token redemption endpoint | ||||||
|   -redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback" |   -redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback" | ||||||
|  |   -resource="": the resource that is being protected. ie: "https://graph.windows.net". Currently only used in the Azure provider. | ||||||
|   -request-logging=true: Log requests to stdout |   -request-logging=true: Log requests to stdout | ||||||
|   -scope="": Oauth scope specification |   -scope="": Oauth scope specification | ||||||
|   -signature-key="": GAP-Signature request signature key (algorithm:secretkey) |   -signature-key="": GAP-Signature request signature key (algorithm:secretkey) | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								main.go
								
								
								
								
							
							
						
						
									
										2
									
								
								main.go
								
								
								
								
							|  | @ -38,6 +38,7 @@ func main() { | ||||||
| 	flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)") | 	flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)") | ||||||
| 
 | 
 | ||||||
| 	flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") | 	flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") | ||||||
|  | 	flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.") | ||||||
| 	flagSet.String("github-org", "", "restrict logins to members of this organisation") | 	flagSet.String("github-org", "", "restrict logins to members of this organisation") | ||||||
| 	flagSet.String("github-team", "", "restrict logins to members of this team") | 	flagSet.String("github-team", "", "restrict logins to members of this team") | ||||||
| 	flagSet.Var(&googleGroups, "google-group", "restrict logins to members of this google group (may be given multiple times).") | 	flagSet.Var(&googleGroups, "google-group", "restrict logins to members of this google group (may be given multiple times).") | ||||||
|  | @ -65,6 +66,7 @@ func main() { | ||||||
| 	flagSet.String("login-url", "", "Authentication endpoint") | 	flagSet.String("login-url", "", "Authentication endpoint") | ||||||
| 	flagSet.String("redeem-url", "", "Token redemption endpoint") | 	flagSet.String("redeem-url", "", "Token redemption endpoint") | ||||||
| 	flagSet.String("profile-url", "", "Profile access endpoint") | 	flagSet.String("profile-url", "", "Profile access endpoint") | ||||||
|  | 	flagSet.String("resource", "", "The resource that is protected (Azure AD only)") | ||||||
| 	flagSet.String("validate-url", "", "Access token validation endpoint") | 	flagSet.String("validate-url", "", "Access token validation endpoint") | ||||||
| 	flagSet.String("scope", "", "OAuth scope specification") | 	flagSet.String("scope", "", "OAuth scope specification") | ||||||
| 	flagSet.String("approval-prompt", "force", "OAuth approval_prompt") | 	flagSet.String("approval-prompt", "force", "OAuth approval_prompt") | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								options.go
								
								
								
								
							
							
						
						
									
										19
									
								
								options.go
								
								
								
								
							|  | @ -25,6 +25,7 @@ type Options struct { | ||||||
| 	TLSKeyFile   string `flag:"tls-key" cfg:"tls_key_file"` | 	TLSKeyFile   string `flag:"tls-key" cfg:"tls_key_file"` | ||||||
| 
 | 
 | ||||||
| 	AuthenticatedEmailsFile  string   `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"` | 	AuthenticatedEmailsFile  string   `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"` | ||||||
|  | 	AzureTenant              string   `flag:"azure-tenant" cfg:"azure_tenant"` | ||||||
| 	EmailDomains             []string `flag:"email-domain" cfg:"email_domains"` | 	EmailDomains             []string `flag:"email-domain" cfg:"email_domains"` | ||||||
| 	GitHubOrg                string   `flag:"github-org" cfg:"github_org"` | 	GitHubOrg                string   `flag:"github-org" cfg:"github_org"` | ||||||
| 	GitHubTeam               string   `flag:"github-team" cfg:"github_team"` | 	GitHubTeam               string   `flag:"github-team" cfg:"github_team"` | ||||||
|  | @ -52,13 +53,14 @@ type Options struct { | ||||||
| 
 | 
 | ||||||
| 	// These options allow for other providers besides Google, with
 | 	// These options allow for other providers besides Google, with
 | ||||||
| 	// potential overrides.
 | 	// potential overrides.
 | ||||||
| 	Provider       string `flag:"provider" cfg:"provider"` | 	Provider          string `flag:"provider" cfg:"provider"` | ||||||
| 	LoginURL       string `flag:"login-url" cfg:"login_url"` | 	LoginURL          string `flag:"login-url" cfg:"login_url"` | ||||||
| 	RedeemURL      string `flag:"redeem-url" cfg:"redeem_url"` | 	RedeemURL         string `flag:"redeem-url" cfg:"redeem_url"` | ||||||
| 	ProfileURL     string `flag:"profile-url" cfg:"profile_url"` | 	ProfileURL        string `flag:"profile-url" cfg:"profile_url"` | ||||||
| 	ValidateURL    string `flag:"validate-url" cfg:"validate_url"` | 	ProtectedResource string `flag:"resource" cfg:"resource"` | ||||||
| 	Scope          string `flag:"scope" cfg:"scope"` | 	ValidateURL       string `flag:"validate-url" cfg:"validate_url"` | ||||||
| 	ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` | 	Scope             string `flag:"scope" cfg:"scope"` | ||||||
|  | 	ApprovalPrompt    string `flag:"approval-prompt" cfg:"approval_prompt"` | ||||||
| 
 | 
 | ||||||
| 	RequestLogging bool `flag:"request-logging" cfg:"request_logging"` | 	RequestLogging bool `flag:"request-logging" cfg:"request_logging"` | ||||||
| 
 | 
 | ||||||
|  | @ -205,9 +207,12 @@ func parseProviderInfo(o *Options, msgs []string) []string { | ||||||
| 	p.RedeemURL, msgs = parseURL(o.RedeemURL, "redeem", msgs) | 	p.RedeemURL, msgs = parseURL(o.RedeemURL, "redeem", msgs) | ||||||
| 	p.ProfileURL, msgs = parseURL(o.ProfileURL, "profile", msgs) | 	p.ProfileURL, msgs = parseURL(o.ProfileURL, "profile", msgs) | ||||||
| 	p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs) | 	p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs) | ||||||
|  | 	p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs) | ||||||
| 
 | 
 | ||||||
| 	o.provider = providers.New(o.Provider, p) | 	o.provider = providers.New(o.Provider, p) | ||||||
| 	switch p := o.provider.(type) { | 	switch p := o.provider.(type) { | ||||||
|  | 	case *providers.AzureProvider: | ||||||
|  | 		p.Configure(o.AzureTenant) | ||||||
| 	case *providers.GitHubProvider: | 	case *providers.GitHubProvider: | ||||||
| 		p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam) | 		p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam) | ||||||
| 	case *providers.GoogleProvider: | 	case *providers.GoogleProvider: | ||||||
|  |  | ||||||
|  | @ -0,0 +1,86 @@ | ||||||
|  | package providers | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/bitly/oauth2_proxy/api" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type AzureProvider struct { | ||||||
|  | 	*ProviderData | ||||||
|  | 	Tenant string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewAzureProvider(p *ProviderData) *AzureProvider { | ||||||
|  | 	p.ProviderName = "Azure" | ||||||
|  | 
 | ||||||
|  | 	if p.ProfileURL == nil || p.ProfileURL.String() == "" { | ||||||
|  | 		p.ProfileURL = &url.URL{ | ||||||
|  | 			Scheme:   "https", | ||||||
|  | 			Host:     "graph.windows.net", | ||||||
|  | 			Path:     "/me", | ||||||
|  | 			RawQuery: "api-version=1.6", | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if p.ProtectedResource == nil || p.ProtectedResource.String() == "" { | ||||||
|  | 		p.ProtectedResource = &url.URL{ | ||||||
|  | 			Scheme: "https", | ||||||
|  | 			Host:   "graph.windows.net", | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if p.Scope == "" { | ||||||
|  | 		p.Scope = "openid" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &AzureProvider{ProviderData: p} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *AzureProvider) Configure(tenant string) { | ||||||
|  | 	p.Tenant = tenant | ||||||
|  | 	if tenant == "" { | ||||||
|  | 		p.Tenant = "common" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if p.LoginURL == nil || p.LoginURL.String() == "" { | ||||||
|  | 		p.LoginURL = &url.URL{ | ||||||
|  | 			Scheme: "https", | ||||||
|  | 			Host:   "login.microsoftonline.com", | ||||||
|  | 			Path:   "/" + p.Tenant + "/oauth2/authorize"} | ||||||
|  | 	} | ||||||
|  | 	if p.RedeemURL == nil || p.RedeemURL.String() == "" { | ||||||
|  | 		p.RedeemURL = &url.URL{ | ||||||
|  | 			Scheme: "https", | ||||||
|  | 			Host:   "login.microsoftonline.com", | ||||||
|  | 			Path:   "/" + p.Tenant + "/oauth2/token", | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getAzureHeader(access_token string) http.Header { | ||||||
|  | 	header := make(http.Header) | ||||||
|  | 	header.Set("Authorization", fmt.Sprintf("Bearer %s", access_token)) | ||||||
|  | 	return header | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *AzureProvider) GetEmailAddress(s *SessionState) (string, error) { | ||||||
|  | 	if s.AccessToken == "" { | ||||||
|  | 		return "", errors.New("missing access token") | ||||||
|  | 	} | ||||||
|  | 	req, err := http.NewRequest("GET", p.ProfileURL.String(), nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	req.Header = getAzureHeader(s.AccessToken) | ||||||
|  | 
 | ||||||
|  | 	json, err := api.Request(req) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("failed making request %s", err) | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return json.Get("mail").String() | ||||||
|  | } | ||||||
|  | @ -0,0 +1,135 @@ | ||||||
|  | package providers | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/bmizerany/assert" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/http/httptest" | ||||||
|  | 	"net/url" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func testAzureProvider(hostname string) *AzureProvider { | ||||||
|  | 	p := NewAzureProvider( | ||||||
|  | 		&ProviderData{ | ||||||
|  | 			ProviderName:      "", | ||||||
|  | 			LoginURL:          &url.URL{}, | ||||||
|  | 			RedeemURL:         &url.URL{}, | ||||||
|  | 			ProfileURL:        &url.URL{}, | ||||||
|  | 			ValidateURL:       &url.URL{}, | ||||||
|  | 			ProtectedResource: &url.URL{}, | ||||||
|  | 			Scope:             ""}) | ||||||
|  | 	if hostname != "" { | ||||||
|  | 		updateURL(p.Data().LoginURL, hostname) | ||||||
|  | 		updateURL(p.Data().RedeemURL, hostname) | ||||||
|  | 		updateURL(p.Data().ProfileURL, hostname) | ||||||
|  | 		updateURL(p.Data().ValidateURL, hostname) | ||||||
|  | 		updateURL(p.Data().ProtectedResource, hostname) | ||||||
|  | 	} | ||||||
|  | 	return p | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestAzureProviderDefaults(t *testing.T) { | ||||||
|  | 	p := testAzureProvider("") | ||||||
|  | 	assert.NotEqual(t, nil, p) | ||||||
|  | 	p.Configure("") | ||||||
|  | 	assert.Equal(t, "Azure", p.Data().ProviderName) | ||||||
|  | 	assert.Equal(t, "common", p.Tenant) | ||||||
|  | 	assert.Equal(t, "https://login.microsoftonline.com/common/oauth2/authorize", | ||||||
|  | 		p.Data().LoginURL.String()) | ||||||
|  | 	assert.Equal(t, "https://login.microsoftonline.com/common/oauth2/token", | ||||||
|  | 		p.Data().RedeemURL.String()) | ||||||
|  | 	assert.Equal(t, "https://graph.windows.net/me?api-version=1.6", | ||||||
|  | 		p.Data().ProfileURL.String()) | ||||||
|  | 	assert.Equal(t, "https://graph.windows.net", | ||||||
|  | 		p.Data().ProtectedResource.String()) | ||||||
|  | 	assert.Equal(t, "", | ||||||
|  | 		p.Data().ValidateURL.String()) | ||||||
|  | 	assert.Equal(t, "openid", p.Data().Scope) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestAzureProviderOverrides(t *testing.T) { | ||||||
|  | 	p := NewAzureProvider( | ||||||
|  | 		&ProviderData{ | ||||||
|  | 			LoginURL: &url.URL{ | ||||||
|  | 				Scheme: "https", | ||||||
|  | 				Host:   "example.com", | ||||||
|  | 				Path:   "/oauth/auth"}, | ||||||
|  | 			RedeemURL: &url.URL{ | ||||||
|  | 				Scheme: "https", | ||||||
|  | 				Host:   "example.com", | ||||||
|  | 				Path:   "/oauth/token"}, | ||||||
|  | 			ProfileURL: &url.URL{ | ||||||
|  | 				Scheme: "https", | ||||||
|  | 				Host:   "example.com", | ||||||
|  | 				Path:   "/oauth/profile"}, | ||||||
|  | 			ValidateURL: &url.URL{ | ||||||
|  | 				Scheme: "https", | ||||||
|  | 				Host:   "example.com", | ||||||
|  | 				Path:   "/oauth/tokeninfo"}, | ||||||
|  | 			ProtectedResource: &url.URL{ | ||||||
|  | 				Scheme: "https", | ||||||
|  | 				Host:   "example.com"}, | ||||||
|  | 			Scope: "profile"}) | ||||||
|  | 	assert.NotEqual(t, nil, p) | ||||||
|  | 	assert.Equal(t, "Azure", p.Data().ProviderName) | ||||||
|  | 	assert.Equal(t, "https://example.com/oauth/auth", | ||||||
|  | 		p.Data().LoginURL.String()) | ||||||
|  | 	assert.Equal(t, "https://example.com/oauth/token", | ||||||
|  | 		p.Data().RedeemURL.String()) | ||||||
|  | 	assert.Equal(t, "https://example.com/oauth/profile", | ||||||
|  | 		p.Data().ProfileURL.String()) | ||||||
|  | 	assert.Equal(t, "https://example.com/oauth/tokeninfo", | ||||||
|  | 		p.Data().ValidateURL.String()) | ||||||
|  | 	assert.Equal(t, "https://example.com", | ||||||
|  | 		p.Data().ProtectedResource.String()) | ||||||
|  | 	assert.Equal(t, "profile", p.Data().Scope) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestAzureSetTenant(t *testing.T) { | ||||||
|  | 	p := testAzureProvider("") | ||||||
|  | 	p.Configure("example") | ||||||
|  | 	assert.Equal(t, "Azure", p.Data().ProviderName) | ||||||
|  | 	assert.Equal(t, "example", p.Tenant) | ||||||
|  | 	assert.Equal(t, "https://login.microsoftonline.com/example/oauth2/authorize", | ||||||
|  | 		p.Data().LoginURL.String()) | ||||||
|  | 	assert.Equal(t, "https://login.microsoftonline.com/example/oauth2/token", | ||||||
|  | 		p.Data().RedeemURL.String()) | ||||||
|  | 	assert.Equal(t, "https://graph.windows.net/me?api-version=1.6", | ||||||
|  | 		p.Data().ProfileURL.String()) | ||||||
|  | 	assert.Equal(t, "https://graph.windows.net", | ||||||
|  | 		p.Data().ProtectedResource.String()) | ||||||
|  | 	assert.Equal(t, "", | ||||||
|  | 		p.Data().ValidateURL.String()) | ||||||
|  | 	assert.Equal(t, "openid", p.Data().Scope) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func testAzureBackend(payload string) *httptest.Server { | ||||||
|  | 	path := "/me" | ||||||
|  | 	query := "api-version=1.6" | ||||||
|  | 
 | ||||||
|  | 	return httptest.NewServer(http.HandlerFunc( | ||||||
|  | 		func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 			url := r.URL | ||||||
|  | 			if url.Path != path || url.RawQuery != query { | ||||||
|  | 				w.WriteHeader(404) | ||||||
|  | 			} else if r.Header.Get("Authorization") != "Bearer imaginary_access_token" { | ||||||
|  | 				w.WriteHeader(403) | ||||||
|  | 			} else { | ||||||
|  | 				w.WriteHeader(200) | ||||||
|  | 				w.Write([]byte(payload)) | ||||||
|  | 			} | ||||||
|  | 		})) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestAzureProviderGetEmailAddress(t *testing.T) { | ||||||
|  | 	b := testAzureBackend(`{ "mail": "user@windows.net" }`) | ||||||
|  | 	defer b.Close() | ||||||
|  | 
 | ||||||
|  | 	b_url, _ := url.Parse(b.URL) | ||||||
|  | 	p := testAzureProvider(b_url.Host) | ||||||
|  | 
 | ||||||
|  | 	session := &SessionState{AccessToken: "imaginary_access_token"} | ||||||
|  | 	email, err := p.GetEmailAddress(session) | ||||||
|  | 	assert.Equal(t, nil, err) | ||||||
|  | 	assert.Equal(t, "user@windows.net", email) | ||||||
|  | } | ||||||
|  | @ -5,15 +5,16 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type ProviderData struct { | type ProviderData struct { | ||||||
| 	ProviderName   string | 	ProviderName      string | ||||||
| 	ClientID       string | 	ClientID          string | ||||||
| 	ClientSecret   string | 	ClientSecret      string | ||||||
| 	LoginURL       *url.URL | 	LoginURL          *url.URL | ||||||
| 	RedeemURL      *url.URL | 	RedeemURL         *url.URL | ||||||
| 	ProfileURL     *url.URL | 	ProfileURL        *url.URL | ||||||
| 	ValidateURL    *url.URL | 	ProtectedResource *url.URL | ||||||
| 	Scope          string | 	ValidateURL       *url.URL | ||||||
| 	ApprovalPrompt string | 	Scope             string | ||||||
|  | 	ApprovalPrompt    string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *ProviderData) Data() *ProviderData { return p } | func (p *ProviderData) Data() *ProviderData { return p } | ||||||
|  |  | ||||||
|  | @ -25,6 +25,10 @@ func (p *ProviderData) Redeem(redirectURL, code string) (s *SessionState, err er | ||||||
| 	params.Add("client_secret", p.ClientSecret) | 	params.Add("client_secret", p.ClientSecret) | ||||||
| 	params.Add("code", code) | 	params.Add("code", code) | ||||||
| 	params.Add("grant_type", "authorization_code") | 	params.Add("grant_type", "authorization_code") | ||||||
|  | 	if p.ProtectedResource != nil && p.ProtectedResource.String() != "" { | ||||||
|  | 		params.Add("resource", p.ProtectedResource.String()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	var req *http.Request | 	var req *http.Request | ||||||
| 	req, err = http.NewRequest("POST", p.RedeemURL.String(), bytes.NewBufferString(params.Encode())) | 	req, err = http.NewRequest("POST", p.RedeemURL.String(), bytes.NewBufferString(params.Encode())) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -24,6 +24,8 @@ func New(provider string, p *ProviderData) Provider { | ||||||
| 		return NewLinkedInProvider(p) | 		return NewLinkedInProvider(p) | ||||||
| 	case "github": | 	case "github": | ||||||
| 		return NewGitHubProvider(p) | 		return NewGitHubProvider(p) | ||||||
|  | 	case "azure": | ||||||
|  | 		return NewAzureProvider(p) | ||||||
| 	default: | 	default: | ||||||
| 		return NewGoogleProvider(p) | 		return NewGoogleProvider(p) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -41,9 +41,8 @@ func WatchForUpdates(filename string, done <-chan bool, action func()) { | ||||||
| 		for { | 		for { | ||||||
| 			select { | 			select { | ||||||
| 			case _ = <-done: | 			case _ = <-done: | ||||||
| 				log.Printf("Shutting down watcher for: %s", | 				log.Printf("Shutting down watcher for: %s", filename) | ||||||
| 					filename) | 				break | ||||||
| 				return |  | ||||||
| 			case event := <-watcher.Events: | 			case event := <-watcher.Events: | ||||||
| 				// On Arch Linux, it appears Chmod events precede Remove events,
 | 				// On Arch Linux, it appears Chmod events precede Remove events,
 | ||||||
| 				// which causes a race between action() and the coming Remove event.
 | 				// which causes a race between action() and the coming Remove event.
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue