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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  },
 | 
			
		||||
]);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}]);
 | 
			
		||||
		Loading…
	
		Reference in New Issue