Compare commits
2 Commits
feat/EE-97
...
feat/EE-93
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77c5e7f8a8 | ||
|
|
0cefd857a4 |
@@ -5,7 +5,6 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
@@ -134,14 +133,6 @@ func (handler *Handler) writeToken(w http.ResponseWriter, user *portainer.User)
|
||||
return handler.persistAndWriteToken(w, composeTokenData(user))
|
||||
}
|
||||
|
||||
func (handler *Handler) writeTokenForOAuth(w http.ResponseWriter, user *portainer.User, expiryTime *time.Time) *httperror.HandlerError {
|
||||
token, err := handler.JWTService.GenerateTokenForOAuth(composeTokenData(user), expiryTime)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to generate JWT token", Err: err}
|
||||
}
|
||||
return response.JSON(w, &authenticateResponse{JWT: token})
|
||||
}
|
||||
|
||||
func (handler *Handler) persistAndWriteToken(w http.ResponseWriter, tokenData *portainer.TokenData) *httperror.HandlerError {
|
||||
token, err := handler.JWTService.GenerateToken(tokenData)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
@@ -26,21 +25,21 @@ func (payload *oauthPayload) Validate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *Handler) authenticateOAuth(code string, settings *portainer.OAuthSettings) (string, *time.Time, error) {
|
||||
func (handler *Handler) authenticateOAuth(code string, settings *portainer.OAuthSettings) (string, error) {
|
||||
if code == "" {
|
||||
return "", nil, errors.New("Invalid OAuth authorization code")
|
||||
return "", errors.New("Invalid OAuth authorization code")
|
||||
}
|
||||
|
||||
if settings == nil {
|
||||
return "", nil, errors.New("Invalid OAuth configuration")
|
||||
return "", errors.New("Invalid OAuth configuration")
|
||||
}
|
||||
|
||||
username, expiryTime, err := handler.OAuthService.Authenticate(code, settings)
|
||||
username, err := handler.OAuthService.Authenticate(code, settings)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
return username, expiryTime, nil
|
||||
return username, nil
|
||||
}
|
||||
|
||||
// @id ValidateOAuth
|
||||
@@ -70,7 +69,7 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
|
||||
return &httperror.HandlerError{StatusCode: http.StatusForbidden, Message: "OAuth authentication is not enabled", Err: errors.New("OAuth authentication is not enabled")}
|
||||
}
|
||||
|
||||
username, expiryTime, err := handler.authenticateOAuth(payload.Code, &settings.OAuthSettings)
|
||||
username, err := handler.authenticateOAuth(payload.Code, &settings.OAuthSettings)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] - OAuth authentication error: %s", err)
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to authenticate through OAuth", Err: httperrors.ErrUnauthorized}
|
||||
@@ -111,5 +110,5 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
|
||||
|
||||
}
|
||||
|
||||
return handler.writeTokenForOAuth(w, user, expiryTime)
|
||||
return handler.writeToken(w, user)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
@@ -27,18 +26,18 @@ func NewService() *Service {
|
||||
// Authenticate takes an access code and exchanges it for an access token from portainer OAuthSettings token endpoint.
|
||||
// On success, it will then return the username and token expiry time associated to authenticated user by fetching this information
|
||||
// from the resource server and matching it with the user identifier setting.
|
||||
func (*Service) Authenticate(code string, configuration *portainer.OAuthSettings) (string, *time.Time, error) {
|
||||
func (*Service) Authenticate(code string, configuration *portainer.OAuthSettings) (string, error) {
|
||||
token, err := getOAuthToken(code, configuration)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] - Failed retrieving access token: %v", err)
|
||||
return "", nil, err
|
||||
return "", err
|
||||
}
|
||||
username, err := getUsername(token.AccessToken, configuration)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] - Failed retrieving oauth user name: %v", err)
|
||||
return "", nil, err
|
||||
return "", err
|
||||
}
|
||||
return username, &token.Expiry, nil
|
||||
return username, nil
|
||||
}
|
||||
|
||||
func getOAuthToken(code string, configuration *portainer.OAuthSettings) (*oauth2.Token, error) {
|
||||
|
||||
@@ -1193,7 +1193,7 @@ type (
|
||||
|
||||
// OAuthService represents a service used to authenticate users using OAuth
|
||||
OAuthService interface {
|
||||
Authenticate(code string, configuration *OAuthSettings) (string, *time.Time, error)
|
||||
Authenticate(code string, configuration *OAuthSettings) (string, error)
|
||||
}
|
||||
|
||||
// RegistryService represents a service for managing registry data
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
angular.module('portainer.oauth').controller('OAuthSettingsController', function OAuthSettingsController() {
|
||||
var ctrl = this;
|
||||
this.addAdminClaimRegex = addAdminClaimRegex;
|
||||
this.removeAdminClaimRegex = removeAdminClaimRegex;
|
||||
|
||||
this.state = {
|
||||
provider: {},
|
||||
@@ -7,6 +9,14 @@ angular.module('portainer.oauth').controller('OAuthSettingsController', function
|
||||
|
||||
this.$onInit = $onInit;
|
||||
|
||||
function addAdminClaimRegex() {
|
||||
ctrl.settings.AdminGroupClaimsRegexList.push('');
|
||||
}
|
||||
|
||||
function removeAdminClaimRegex(index) {
|
||||
ctrl.settings.AdminGroupClaimsRegexList.splice(index, 1);
|
||||
}
|
||||
|
||||
function $onInit() {
|
||||
if (ctrl.settings.RedirectURI === '') {
|
||||
ctrl.settings.RedirectURI = window.location.origin;
|
||||
@@ -19,5 +29,7 @@ angular.module('portainer.oauth').controller('OAuthSettingsController', function
|
||||
if (ctrl.settings.DefaultTeamID === 0) {
|
||||
ctrl.settings.DefaultTeamID = null;
|
||||
}
|
||||
|
||||
ctrl.settings.AdminGroupClaimsRegexList = [];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -82,6 +82,36 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-12 control-label text-left">Admin mapping</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="admin-auto-populate" class="text-muted small" style="margin-bottom: 0.5em; vertical-align: top;">
|
||||
Assign admin rights to group(s) <portainer-tooltip position="bottom" message="Complete OAuth configuration below"></portainer-tooltip
|
||||
></label>
|
||||
<label class="switch" style="margin: 0 30px; vertical-align: top;">
|
||||
<input id="admin-auto-populate" type="checkbox" ng-model="$ctrl.settings.AdminAutoPopulate" /><i></i>
|
||||
</label>
|
||||
<div style="display: inline-block;" ng-if="$ctrl.settings.AdminAutoPopulate">
|
||||
<span class="label label-default interactive" style="margin-left: 1.4em;" ng-click="$ctrl.addAdminClaimRegex()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add admin mapping
|
||||
</span>
|
||||
|
||||
<div class="form-inline" ng-repeat="mapping in $ctrl.settings.AdminGroupClaimsRegexList track by $index" style="margin-top: 0.75em;">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon">claim value regex</span>
|
||||
<input style="min-width: 300px;" id="admin-claims_regex_{{ $index }}" type="text" class="form-control" ng-model="$ctrl.settings.AdminGroupClaimsRegexList[$index]" />
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-danger" ng-click="$ctrl.removeAdminClaimRegex($index)"> <i class="fa fa-trash" aria-hidden="true"> </i></button>
|
||||
<div class="small text-warning" ng-show="!$ctrl.settings.AdminGroupClaimsRegexList[$index]" style="margin-top: 0.4em;">
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Claim value regex is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 form-section-title">OAuth Configuration</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
||||
@@ -425,7 +425,13 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="saveSettings()" ng-disabled="state.actionInProgress" button-spinner="state.actionInProgress">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-click="saveSettings()"
|
||||
ng-disabled="state.actionInProgress || (settings.AuthenticationMethod === 3 && !isOAuthAdminMappingFormValid())"
|
||||
button-spinner="state.actionInProgress"
|
||||
>
|
||||
<span ng-hide="state.actionInProgress">Save settings</span>
|
||||
<span ng-show="state.actionInProgress">Saving...</span>
|
||||
</button>
|
||||
|
||||
@@ -123,6 +123,11 @@ angular.module('portainer.app').controller('SettingsAuthenticationController', [
|
||||
|
||||
$scope.saveSettings = function () {
|
||||
var settings = angular.copy($scope.settings);
|
||||
|
||||
if (!settings.OAuthSettings.AdminAutoPopulate) {
|
||||
delete settings.OAuthSettings.AdminGroupClaimsRegexList;
|
||||
}
|
||||
|
||||
var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null;
|
||||
|
||||
if ($scope.formValues.LDAPSettings.AnonymousMode) {
|
||||
@@ -151,6 +156,18 @@ angular.module('portainer.app').controller('SettingsAuthenticationController', [
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isOAuthAdminMappingFormValid = function () {
|
||||
if ($scope.settings && $scope.settings.OAuthSettings.AdminAutoPopulate && $scope.settings.OAuthSettings.AdminGroupClaimsRegexList) {
|
||||
const hasInvalidMapping =
|
||||
$scope.settings.OAuthSettings.AdminGroupClaimsRegexList.length === 0 || $scope.settings.OAuthSettings.AdminGroupClaimsRegexList.some((e) => e === '');
|
||||
if (hasInvalidMapping) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Add default port if :port is not defined in URL
|
||||
function addLDAPDefaultPort(settings, tlsEnabled) {
|
||||
if (settings.LDAPSettings.URL.indexOf(':') === -1) {
|
||||
|
||||
Reference in New Issue
Block a user