mirror of https://github.com/portainer/portainer
feat(setting/oauth): add authstyle option [EE-6038] (#11590)
parent
a755e6be15
commit
ccfd5e4500
|
@ -631,6 +631,7 @@
|
||||||
"LogoURL": "",
|
"LogoURL": "",
|
||||||
"OAuthSettings": {
|
"OAuthSettings": {
|
||||||
"AccessTokenURI": "",
|
"AccessTokenURI": "",
|
||||||
|
"AuthStyle": 0,
|
||||||
"AuthorizationURI": "",
|
"AuthorizationURI": "",
|
||||||
"ClientID": "",
|
"ClientID": "",
|
||||||
"DefaultTeamID": 0,
|
"DefaultTeamID": 0,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -95,6 +96,9 @@ func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if payload.OAuthSettings.AuthStyle < oauth2.AuthStyleAutoDetect || payload.OAuthSettings.AuthStyle > oauth2.AuthStyleInHeader {
|
||||||
|
return errors.New("Invalid OAuth AuthStyle")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,6 +229,7 @@ func (handler *Handler) updateSettings(tx dataservices.DataStoreTx, payload sett
|
||||||
settings.OAuthSettings = *payload.OAuthSettings
|
settings.OAuthSettings = *payload.OAuthSettings
|
||||||
settings.OAuthSettings.ClientSecret = clientSecret
|
settings.OAuthSettings.ClientSecret = clientSecret
|
||||||
settings.OAuthSettings.KubeSecretKey = kubeSecret
|
settings.OAuthSettings.KubeSecretKey = kubeSecret
|
||||||
|
settings.OAuthSettings.AuthStyle = payload.OAuthSettings.AuthStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.EnableEdgeComputeFeatures != nil {
|
if payload.EnableEdgeComputeFeatures != nil {
|
||||||
|
|
|
@ -174,6 +174,7 @@ func buildConfig(configuration *portainer.OAuthSettings) *oauth2.Config {
|
||||||
endpoint := oauth2.Endpoint{
|
endpoint := oauth2.Endpoint{
|
||||||
AuthURL: configuration.AuthorizationURI,
|
AuthURL: configuration.AuthorizationURI,
|
||||||
TokenURL: configuration.AccessTokenURI,
|
TokenURL: configuration.AccessTokenURI,
|
||||||
|
AuthStyle: configuration.AuthStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &oauth2.Config{
|
return &oauth2.Config{
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
gittypes "github.com/portainer/portainer/api/git/types"
|
gittypes "github.com/portainer/portainer/api/git/types"
|
||||||
models "github.com/portainer/portainer/api/http/models/kubernetes"
|
models "github.com/portainer/portainer/api/http/models/kubernetes"
|
||||||
"github.com/portainer/portainer/pkg/featureflags"
|
"github.com/portainer/portainer/pkg/featureflags"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -771,6 +772,7 @@ type (
|
||||||
SSO bool `json:"SSO"`
|
SSO bool `json:"SSO"`
|
||||||
LogoutURI string `json:"LogoutURI"`
|
LogoutURI string `json:"LogoutURI"`
|
||||||
KubeSecretKey []byte `json:"KubeSecretKey"`
|
KubeSecretKey []byte `json:"KubeSecretKey"`
|
||||||
|
AuthStyle oauth2.AuthStyle `json:"AuthStyle"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pair defines a key/value string pair
|
// Pair defines a key/value string pair
|
||||||
|
|
|
@ -78,6 +78,7 @@ export function OAuthSettingsViewModel(data) {
|
||||||
this.DefaultTeamID = data.DefaultTeamID;
|
this.DefaultTeamID = data.DefaultTeamID;
|
||||||
this.SSO = data.SSO;
|
this.SSO = data.SSO;
|
||||||
this.LogoutURI = data.LogoutURI;
|
this.LogoutURI = data.LogoutURI;
|
||||||
|
this.AuthStyle = data.AuthStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EdgeSettingsViewModel(data = {}) {
|
export function EdgeSettingsViewModel(data = {}) {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { isLimitedToBE } from '@/react/portainer/feature-flags/feature-flags.ser
|
||||||
import { ModalType } from '@@/modals';
|
import { ModalType } from '@@/modals';
|
||||||
import { confirm } from '@@/modals/confirm';
|
import { confirm } from '@@/modals/confirm';
|
||||||
import { buildConfirmButton } from '@@/modals/utils';
|
import { buildConfirmButton } from '@@/modals/utils';
|
||||||
|
|
||||||
import providers, { getProviderByUrl } from './providers';
|
import providers, { getProviderByUrl } from './providers';
|
||||||
|
|
||||||
const MS_TENANT_ID_PLACEHOLDER = 'TENANT_ID';
|
const MS_TENANT_ID_PLACEHOLDER = 'TENANT_ID';
|
||||||
|
@ -31,6 +30,7 @@ export default class OAuthSettingsController {
|
||||||
this.addTeamMembershipMapping = this.addTeamMembershipMapping.bind(this);
|
this.addTeamMembershipMapping = this.addTeamMembershipMapping.bind(this);
|
||||||
this.removeTeamMembership = this.removeTeamMembership.bind(this);
|
this.removeTeamMembership = this.removeTeamMembership.bind(this);
|
||||||
this.onToggleAutoTeamMembership = this.onToggleAutoTeamMembership.bind(this);
|
this.onToggleAutoTeamMembership = this.onToggleAutoTeamMembership.bind(this);
|
||||||
|
this.onChangeAuthStyle = this.onChangeAuthStyle.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMicrosoftTenantIDChange() {
|
onMicrosoftTenantIDChange() {
|
||||||
|
@ -54,6 +54,7 @@ export default class OAuthSettingsController {
|
||||||
this.settings.LogoutURI = provider.logoutUrl;
|
this.settings.LogoutURI = provider.logoutUrl;
|
||||||
this.settings.UserIdentifier = provider.userIdentifier;
|
this.settings.UserIdentifier = provider.userIdentifier;
|
||||||
this.settings.Scopes = provider.scopes;
|
this.settings.Scopes = provider.scopes;
|
||||||
|
this.settings.AuthStyle = provider.authStyle;
|
||||||
|
|
||||||
if (providerId === 'microsoft' && this.state.microsoftTenantID !== '') {
|
if (providerId === 'microsoft' && this.state.microsoftTenantID !== '') {
|
||||||
this.onMicrosoftTenantIDChange();
|
this.onMicrosoftTenantIDChange();
|
||||||
|
@ -77,6 +78,12 @@ export default class OAuthSettingsController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeAuthStyle(val) {
|
||||||
|
this.$scope.$evalAsync(() => {
|
||||||
|
this.settings.AuthStyle = val;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async onChangeHideInternalAuth(checked) {
|
async onChangeHideInternalAuth(checked) {
|
||||||
this.$async(async () => {
|
this.$async(async () => {
|
||||||
if (this.isLimitedToBE) {
|
if (this.isLimitedToBE) {
|
||||||
|
|
|
@ -341,6 +341,8 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<oauth-auth-style value="$ctrl.settings.AuthStyle" on-change="($ctrl.onChangeAuthStyle)"></oauth-auth-style>
|
||||||
<save-auth-settings-button
|
<save-auth-settings-button
|
||||||
on-save-settings="($ctrl.onSaveSettings)"
|
on-save-settings="($ctrl.onSaveSettings)"
|
||||||
save-button-state="($ctrl.saveButtonState)"
|
save-button-state="($ctrl.saveButtonState)"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { baseHref } from '@/portainer/helpers/pathHelper';
|
import { baseHref } from '@/portainer/helpers/pathHelper';
|
||||||
|
import { OAuthStyle } from '@/react/portainer/settings/types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
microsoft: {
|
microsoft: {
|
||||||
|
@ -8,6 +9,7 @@ export default {
|
||||||
logoutUrl: `https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/logout`,
|
logoutUrl: `https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/logout`,
|
||||||
userIdentifier: 'userPrincipalName',
|
userIdentifier: 'userPrincipalName',
|
||||||
scopes: 'profile openid',
|
scopes: 'profile openid',
|
||||||
|
authStyle: OAuthStyle.InParams,
|
||||||
},
|
},
|
||||||
google: {
|
google: {
|
||||||
authUrl: 'https://accounts.google.com/o/oauth2/auth',
|
authUrl: 'https://accounts.google.com/o/oauth2/auth',
|
||||||
|
@ -16,6 +18,7 @@ export default {
|
||||||
logoutUrl: `https://www.google.com/accounts/Logout?continue=https://appengine.google.com/_ah/logout?continue=${window.location.origin}${baseHref()}#!/auth`,
|
logoutUrl: `https://www.google.com/accounts/Logout?continue=https://appengine.google.com/_ah/logout?continue=${window.location.origin}${baseHref()}#!/auth`,
|
||||||
userIdentifier: 'email',
|
userIdentifier: 'email',
|
||||||
scopes: 'profile email',
|
scopes: 'profile email',
|
||||||
|
authStyle: OAuthStyle.InParams,
|
||||||
},
|
},
|
||||||
github: {
|
github: {
|
||||||
authUrl: 'https://github.com/login/oauth/authorize',
|
authUrl: 'https://github.com/login/oauth/authorize',
|
||||||
|
@ -24,8 +27,9 @@ export default {
|
||||||
logoutUrl: `https://github.com/logout`,
|
logoutUrl: `https://github.com/logout`,
|
||||||
userIdentifier: 'login',
|
userIdentifier: 'login',
|
||||||
scopes: 'id email name',
|
scopes: 'id email name',
|
||||||
|
authStyle: OAuthStyle.AutoDetect,
|
||||||
},
|
},
|
||||||
custom: { authUrl: '', accessTokenUrl: '', resourceUrl: '', logoutUrl: '', userIdentifier: '', scopes: '' },
|
custom: { authUrl: '', accessTokenUrl: '', resourceUrl: '', logoutUrl: '', userIdentifier: '', scopes: '', authStyle: OAuthStyle.AutoDetect },
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getProviderByUrl(providerAuthURL = '') {
|
export function getProviderByUrl(providerAuthURL = '') {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { KubeSettingsPanel } from '@/react/portainer/settings/SettingsView/KubeS
|
||||||
import { HelmCertPanel } from '@/react/portainer/settings/SettingsView/HelmCertPanel';
|
import { HelmCertPanel } from '@/react/portainer/settings/SettingsView/HelmCertPanel';
|
||||||
import { HiddenContainersPanel } from '@/react/portainer/settings/SettingsView/HiddenContainersPanel/HiddenContainersPanel';
|
import { HiddenContainersPanel } from '@/react/portainer/settings/SettingsView/HiddenContainersPanel/HiddenContainersPanel';
|
||||||
import { SSLSettingsPanelWrapper } from '@/react/portainer/settings/SettingsView/SSLSettingsPanel/SSLSettingsPanel';
|
import { SSLSettingsPanelWrapper } from '@/react/portainer/settings/SettingsView/SSLSettingsPanel/SSLSettingsPanel';
|
||||||
|
import { AuthStyleField } from '@/react/portainer/settings/AuthenticationView/OAuth';
|
||||||
|
|
||||||
export const settingsModule = angular
|
export const settingsModule = angular
|
||||||
.module('portainer.app.react.components.settings', [])
|
.module('portainer.app.react.components.settings', [])
|
||||||
|
@ -39,4 +40,15 @@ export const settingsModule = angular
|
||||||
.component(
|
.component(
|
||||||
'kubeSettingsPanel',
|
'kubeSettingsPanel',
|
||||||
r2a(withUIRouter(withReactQuery(KubeSettingsPanel)), ['settings'])
|
r2a(withUIRouter(withReactQuery(KubeSettingsPanel)), ['settings'])
|
||||||
|
)
|
||||||
|
.component(
|
||||||
|
'oauthAuthStyle',
|
||||||
|
r2a(AuthStyleField, [
|
||||||
|
'value',
|
||||||
|
'onChange',
|
||||||
|
'label',
|
||||||
|
'tooltip',
|
||||||
|
'readonly',
|
||||||
|
'size',
|
||||||
|
])
|
||||||
).name;
|
).name;
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { Options } from '@/react/edge/components/useIntervalOptions';
|
||||||
|
import { OAuthStyle } from '@/react/portainer/settings/types';
|
||||||
|
|
||||||
|
import { FormControl, Size } from '@@/form-components/FormControl';
|
||||||
|
import { Select } from '@@/form-components/Input';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: OAuthStyle;
|
||||||
|
onChange(value: OAuthStyle): void;
|
||||||
|
label?: string;
|
||||||
|
tooltip?: string;
|
||||||
|
readonly?: boolean;
|
||||||
|
size?: Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The options are based on oauth2 lib definition @https://pkg.go.dev/golang.org/x/oauth2#AuthStyle
|
||||||
|
export const authStyleOptions: Options = [
|
||||||
|
{ label: 'Auto Detect', value: OAuthStyle.AutoDetect, isDefault: true },
|
||||||
|
{ label: 'In Params', value: OAuthStyle.InParams },
|
||||||
|
{ label: 'In Header', value: OAuthStyle.InHeader },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function AuthStyleField({
|
||||||
|
value,
|
||||||
|
readonly = false,
|
||||||
|
onChange,
|
||||||
|
label = 'Auth Style',
|
||||||
|
tooltip = 'Auth Style specifies how the endpoint wants the client ID & client secret sent.',
|
||||||
|
size = 'small',
|
||||||
|
}: Props) {
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
inputId="oauth_authstyle"
|
||||||
|
label={label}
|
||||||
|
tooltip={tooltip}
|
||||||
|
size={size}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => {
|
||||||
|
onChange(parseInt(e.currentTarget.value, 10));
|
||||||
|
}}
|
||||||
|
options={authStyleOptions}
|
||||||
|
disabled={readonly}
|
||||||
|
id="oauth_authstyle"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export { AuthStyleField } from './AuthStyleField';
|
|
@ -87,6 +87,15 @@ export enum AuthenticationMethod {
|
||||||
OAuth,
|
OAuth,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The definition are based on oauth2 lib definition @https://pkg.go.dev/golang.org/x/oauth2#AuthStyle
|
||||||
|
*/
|
||||||
|
export enum OAuthStyle {
|
||||||
|
AutoDetect = 0,
|
||||||
|
InParams,
|
||||||
|
InHeader,
|
||||||
|
}
|
||||||
|
|
||||||
type Feature = string;
|
type Feature = string;
|
||||||
|
|
||||||
export interface DefaultRegistry {
|
export interface DefaultRegistry {
|
||||||
|
|
Loading…
Reference in New Issue