diff --git a/api/http/handler/auth/authenticate.go b/api/http/handler/auth/authenticate.go index 8183c2f7b..d5562edd3 100644 --- a/api/http/handler/auth/authenticate.go +++ b/api/http/handler/auth/authenticate.go @@ -17,10 +17,6 @@ type authenticatePayload struct { Password string } -type oauthPayload struct { - Code string -} - type authenticateResponse struct { JWT string `json:"jwt"` } @@ -35,13 +31,6 @@ func (payload *authenticatePayload) Validate(r *http.Request) error { return nil } -func (payload *oauthPayload) Validate(r *http.Request) error { - if govalidator.IsNull(payload.Code) { - return portainer.Error("Invalid OAuth authorization code") - } - return nil -} - func (handler *Handler) authenticate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { if handler.authDisabled { return &httperror.HandlerError{http.StatusServiceUnavailable, "Cannot authenticate user. Portainer was started with the --no-auth flag", ErrAuthDisabled} diff --git a/api/http/handler/auth/authenticate_oauth.go b/api/http/handler/auth/authenticate_oauth.go index 58319b9df..2e311c8b8 100644 --- a/api/http/handler/auth/authenticate_oauth.go +++ b/api/http/handler/auth/authenticate_oauth.go @@ -3,12 +3,27 @@ package auth import ( "log" "net/http" + "strings" + "golang.org/x/oauth2" + + "github.com/asaskevich/govalidator" httperror "github.com/portainer/libhttp/error" "github.com/portainer/libhttp/request" "github.com/portainer/portainer" ) +type oauthPayload struct { + Code string +} + +func (payload *oauthPayload) Validate(r *http.Request) error { + if govalidator.IsNull(payload.Code) { + return portainer.Error("Invalid OAuth authorization code") + } + return nil +} + func (handler *Handler) authenticateOAuth(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { var payload oauthPayload err := request.DecodeAndValidateJSONPayload(r, &payload) @@ -62,3 +77,31 @@ func (handler *Handler) authenticateOAuth(w http.ResponseWriter, r *http.Request return handler.writeToken(w, u) } + +func (handler *Handler) loginOAuth(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + settings, err := handler.SettingsService.Settings() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err} + } + + if settings.AuthenticationMethod != 3 { + return &httperror.HandlerError{http.StatusForbidden, "OAuth authentication is disabled", err} + } + + endpoint := oauth2.Endpoint{ + AuthURL: settings.OAuthSettings.AuthorizationURI, + TokenURL: settings.OAuthSettings.AccessTokenURI, + } + + oauthConfig := &oauth2.Config{ + ClientID: settings.OAuthSettings.ClientID, + ClientSecret: settings.OAuthSettings.ClientSecret, + Endpoint: endpoint, + RedirectURL: settings.OAuthSettings.RedirectURI, + Scopes: strings.Split(settings.OAuthSettings.Scopes, ","), + } + + url := oauthConfig.AuthCodeURL("portainer") + http.Redirect(w, r, url, http.StatusTemporaryRedirect) + return nil +} diff --git a/api/http/handler/auth/handler.go b/api/http/handler/auth/handler.go index a97928150..4bc440a63 100644 --- a/api/http/handler/auth/handler.go +++ b/api/http/handler/auth/handler.go @@ -38,7 +38,9 @@ func NewHandler(bouncer *security.RequestBouncer, rateLimiter *security.RateLimi authDisabled: authDisabled, } - h.Handle("/auth/oauth", + h.Handle("/auth/oauth/login", + rateLimiter.LimitAccess(bouncer.PublicAccess(httperror.LoggerHandler(h.loginOAuth)))).Methods(http.MethodGet) + h.Handle("/auth/oauth/validate", rateLimiter.LimitAccess(bouncer.PublicAccess(httperror.LoggerHandler(h.authenticateOAuth)))).Methods(http.MethodPost) h.Handle("/auth", rateLimiter.LimitAccess(bouncer.PublicAccess(httperror.LoggerHandler(h.authenticate)))).Methods(http.MethodPost) diff --git a/app/extensions/oauth/services/oauth-service.js b/app/extensions/oauth/services/oauth-service.js index d1985c693..88221dd5b 100644 --- a/app/extensions/oauth/services/oauth-service.js +++ b/app/extensions/oauth/services/oauth-service.js @@ -1,28 +1,16 @@ angular.module('portainer.extensions.oauth').service('OAuthService', [ - 'SettingsService', 'OAuth', 'urlHelper', - function OAuthService(SettingsService, OAuth, urlHelper) { + 'API_ENDPOINT_OAUTH', 'OAuth', 'urlHelper', 'Notifications', + function OAuthService(API_ENDPOINT_OAUTH, OAuth, urlHelper, Notifications) { this.login = login; function login() { - return getLoginURI() - .then(function openPopup(loginUrl) { - var popup = window.open(loginUrl, 'login-popup', 'width=800, height=600'); - if (!popup) { - throw new Error('Please enable popups for this page'); - } - return waitForCode(popup); - }) - .then(function onCodeReady(code) { - return OAuth.login({ code: code }).$promise; - }); - } - - function getLoginURI() { - return SettingsService.publicSettings().then(function onLoadSettings(settings) { - if (settings.AuthenticationMethod !== 3) { - throw new Error('OAuth is disabled'); - } - return settings.OAuthLoginURI; + var loginUrl = API_ENDPOINT_OAUTH + '/login'; + var popup = window.open(loginUrl, 'login-popup', 'width=800, height=600'); + if (!popup) { + Notifications.warn('Please enable popups for this page'); + } + return waitForCode(popup).then(function onCodeReady(code) { + return OAuth.validate({ code: code }).$promise; }); } @@ -49,5 +37,5 @@ angular.module('portainer.extensions.oauth').service('OAuthService', [ }, interval); }); } - } + }, ]); diff --git a/app/extensions/oauth/services/rest/oauth.js b/app/extensions/oauth/services/rest/oauth.js index a6eff3bf7..f33e7b30f 100644 --- a/app/extensions/oauth/services/rest/oauth.js +++ b/app/extensions/oauth/services/rest/oauth.js @@ -1,9 +1,13 @@ angular.module('portainer.extensions.oauth') .factory('OAuth', ['$resource', 'API_ENDPOINT_OAUTH', function OAuthFactory($resource, API_ENDPOINT_OAUTH) { 'use strict'; - return $resource(API_ENDPOINT_OAUTH, {}, { - login: { - method: 'POST', ignoreLoadingBar: true + return $resource(API_ENDPOINT_OAUTH + '/:action', {}, { + validate: { + method: 'POST', + ignoreLoadingBar: true, + params: { + action: 'validate' + } } }); }]); \ No newline at end of file