mirror of https://github.com/portainer/portainer
refactor(oauth): use oauth2 to generate login url
parent
0a439b3893
commit
c28274667d
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -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'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}]);
|
}]);
|
Loading…
Reference in New Issue