feat(setting/oauth): add authstyle option [EE-6038] (#11590)

pull/11653/head
Oscar Zhou 7 months ago committed by GitHub
parent a755e6be15
commit ccfd5e4500
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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 {

@ -172,8 +172,9 @@ func getResource(token string, configuration *portainer.OAuthSettings) (map[stri
func buildConfig(configuration *portainer.OAuthSettings) *oauth2.Config { 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"
) )
@ -758,19 +759,20 @@ type (
// OAuthSettings represents the settings used to authorize with an authorization server // OAuthSettings represents the settings used to authorize with an authorization server
OAuthSettings struct { OAuthSettings struct {
ClientID string `json:"ClientID"` ClientID string `json:"ClientID"`
ClientSecret string `json:"ClientSecret,omitempty"` ClientSecret string `json:"ClientSecret,omitempty"`
AccessTokenURI string `json:"AccessTokenURI"` AccessTokenURI string `json:"AccessTokenURI"`
AuthorizationURI string `json:"AuthorizationURI"` AuthorizationURI string `json:"AuthorizationURI"`
ResourceURI string `json:"ResourceURI"` ResourceURI string `json:"ResourceURI"`
RedirectURI string `json:"RedirectURI"` RedirectURI string `json:"RedirectURI"`
UserIdentifier string `json:"UserIdentifier"` UserIdentifier string `json:"UserIdentifier"`
Scopes string `json:"Scopes"` Scopes string `json:"Scopes"`
OAuthAutoCreateUsers bool `json:"OAuthAutoCreateUsers"` OAuthAutoCreateUsers bool `json:"OAuthAutoCreateUsers"`
DefaultTeamID TeamID `json:"DefaultTeamID"` DefaultTeamID TeamID `json:"DefaultTeamID"`
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…
Cancel
Save