refactor(oauth): use oauth2 to generate login url

pull/2749/head
Chaim Lev Ari 2019-01-18 10:13:33 +02:00
parent 0a439b3893
commit c28274667d
5 changed files with 63 additions and 37 deletions

View File

@ -17,10 +17,6 @@ type authenticatePayload struct {
Password string Password string
} }
type oauthPayload struct {
Code string
}
type authenticateResponse struct { type authenticateResponse struct {
JWT string `json:"jwt"` JWT string `json:"jwt"`
} }
@ -35,13 +31,6 @@ func (payload *authenticatePayload) Validate(r *http.Request) error {
return nil 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 { func (handler *Handler) authenticate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
if handler.authDisabled { if handler.authDisabled {
return &httperror.HandlerError{http.StatusServiceUnavailable, "Cannot authenticate user. Portainer was started with the --no-auth flag", ErrAuthDisabled} return &httperror.HandlerError{http.StatusServiceUnavailable, "Cannot authenticate user. Portainer was started with the --no-auth flag", ErrAuthDisabled}

View File

@ -3,12 +3,27 @@ package auth
import ( import (
"log" "log"
"net/http" "net/http"
"strings"
"golang.org/x/oauth2"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request" "github.com/portainer/libhttp/request"
"github.com/portainer/portainer" "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 { func (handler *Handler) authenticateOAuth(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload oauthPayload var payload oauthPayload
err := request.DecodeAndValidateJSONPayload(r, &payload) err := request.DecodeAndValidateJSONPayload(r, &payload)
@ -62,3 +77,31 @@ func (handler *Handler) authenticateOAuth(w http.ResponseWriter, r *http.Request
return handler.writeToken(w, u) 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
}

View File

@ -38,7 +38,9 @@ func NewHandler(bouncer *security.RequestBouncer, rateLimiter *security.RateLimi
authDisabled: authDisabled, 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) rateLimiter.LimitAccess(bouncer.PublicAccess(httperror.LoggerHandler(h.authenticateOAuth)))).Methods(http.MethodPost)
h.Handle("/auth", h.Handle("/auth",
rateLimiter.LimitAccess(bouncer.PublicAccess(httperror.LoggerHandler(h.authenticate)))).Methods(http.MethodPost) rateLimiter.LimitAccess(bouncer.PublicAccess(httperror.LoggerHandler(h.authenticate)))).Methods(http.MethodPost)

View File

@ -1,28 +1,16 @@
angular.module('portainer.extensions.oauth').service('OAuthService', [ angular.module('portainer.extensions.oauth').service('OAuthService', [
'SettingsService', 'OAuth', 'urlHelper', 'API_ENDPOINT_OAUTH', 'OAuth', 'urlHelper', 'Notifications',
function OAuthService(SettingsService, OAuth, urlHelper) { function OAuthService(API_ENDPOINT_OAUTH, OAuth, urlHelper, Notifications) {
this.login = login; this.login = login;
function login() { function login() {
return getLoginURI() var loginUrl = API_ENDPOINT_OAUTH + '/login';
.then(function openPopup(loginUrl) { var popup = window.open(loginUrl, 'login-popup', 'width=800, height=600');
var popup = window.open(loginUrl, 'login-popup', 'width=800, height=600'); if (!popup) {
if (!popup) { Notifications.warn('Please enable popups for this page');
throw new Error('Please enable popups for this page'); }
} return waitForCode(popup).then(function onCodeReady(code) {
return waitForCode(popup); return OAuth.validate({ code: code }).$promise;
})
.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;
}); });
} }
@ -49,5 +37,5 @@ angular.module('portainer.extensions.oauth').service('OAuthService', [
}, interval); }, interval);
}); });
} }
} },
]); ]);

View File

@ -1,9 +1,13 @@
angular.module('portainer.extensions.oauth') angular.module('portainer.extensions.oauth')
.factory('OAuth', ['$resource', 'API_ENDPOINT_OAUTH', function OAuthFactory($resource, API_ENDPOINT_OAUTH) { .factory('OAuth', ['$resource', 'API_ENDPOINT_OAUTH', function OAuthFactory($resource, API_ENDPOINT_OAUTH) {
'use strict'; 'use strict';
return $resource(API_ENDPOINT_OAUTH, {}, { return $resource(API_ENDPOINT_OAUTH + '/:action', {}, {
login: { validate: {
method: 'POST', ignoreLoadingBar: true method: 'POST',
ignoreLoadingBar: true,
params: {
action: 'validate'
}
} }
}); });
}]); }]);