Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
d7e43926
Commit
d7e43926
authored
Dec 10, 2021
by
Peter Hegman
Committed by
Andrew Fontaine
Dec 10, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Convert SCIM token management to Vue
parent
a2ae997e
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
634 additions
and
47 deletions
+634
-47
app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue
...e_shared/components/form/input_copy_toggle_visibility.vue
+3
-0
config/feature_flags/development/scim_token_vue.yml
config/feature_flags/development/scim_token_vue.yml
+8
-0
ee/app/assets/javascripts/pages/groups/saml_providers/index.js
...p/assets/javascripts/pages/groups/saml_providers/index.js
+4
-0
ee/app/assets/javascripts/pages/groups/saml_providers/shared/init_saml.js
...vascripts/pages/groups/saml_providers/shared/init_saml.js
+0
-9
ee/app/assets/javascripts/saml_sso/components/scim_token.vue
ee/app/assets/javascripts/saml_sso/components/scim_token.vue
+190
-0
ee/app/assets/javascripts/saml_sso/index.js
ee/app/assets/javascripts/saml_sso/index.js
+36
-0
ee/app/views/groups/saml_providers/_scim_token.html.haml
ee/app/views/groups/saml_providers/_scim_token.html.haml
+22
-19
ee/spec/features/groups/scim_token_spec.rb
ee/spec/features/groups/scim_token_spec.rb
+86
-19
ee/spec/frontend/fixtures/saml_providers.rb
ee/spec/frontend/fixtures/saml_providers.rb
+1
-0
ee/spec/frontend/saml_sso/components/scim_token_spec.js
ee/spec/frontend/saml_sso/components/scim_token_spec.js
+246
-0
locale/gitlab.pot
locale/gitlab.pot
+27
-0
spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js
...ared/components/form/input_copy_toggle_visibility_spec.js
+11
-0
No files found.
app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue
View file @
d7e43926
...
@@ -120,5 +120,8 @@ export default {
...
@@ -120,5 +120,8 @@ export default {
/>
/>
</
template
>
</
template
>
</gl-form-input-group>
</gl-form-input-group>
<
template
v-for=
"slot in Object.keys($slots)"
#
[
slot
]
>
<slot
:name=
"slot"
></slot>
</
template
>
</gl-form-group>
</gl-form-group>
</template>
</template>
config/feature_flags/development/scim_token_vue.yml
0 → 100644
View file @
d7e43926
---
name
:
scim_token_vue
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74743
rollout_issue_url
:
https://gitlab.com/gitlab-org/gitlab/-/issues/347270
milestone
:
'
14.6'
type
:
development
group
:
group::access
default_enabled
:
false
ee/app/assets/javascripts/pages/groups/saml_providers/index.js
View file @
d7e43926
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
{
initScimTokenApp
}
from
'
ee/saml_sso
'
;
import
MembersApp
from
'
./saml_members/index.vue
'
;
import
MembersApp
from
'
./saml_members/index.vue
'
;
import
createStore
from
'
./saml_members/store
'
;
import
createStore
from
'
./saml_members/store
'
;
import
initSAML
from
'
./shared/init_saml
'
;
import
initSAML
from
'
./shared/init_saml
'
;
...
@@ -21,3 +24,4 @@ function initMembers(el) {
...
@@ -21,3 +24,4 @@ function initMembers(el) {
const
el
=
document
.
querySelector
(
'
.js-saml-members
'
);
const
el
=
document
.
querySelector
(
'
.js-saml-members
'
);
initMembers
(
el
);
initMembers
(
el
);
initSAML
();
initSAML
();
initScimTokenApp
();
ee/app/assets/javascripts/pages/groups/saml_providers/shared/init_saml.js
View file @
d7e43926
import
SamlSettingsForm
from
'
ee/saml_providers/saml_settings_form
'
;
import
SamlSettingsForm
from
'
ee/saml_providers/saml_settings_form
'
;
import
SCIMTokenToggleArea
from
'
ee/saml_providers/scim_token_toggle_area
'
;
export
default
function
initSAML
()
{
export
default
function
initSAML
()
{
const
groupPath
=
document
.
querySelector
(
'
#issuer
'
).
value
;
// eslint-disable-next-line no-new
new
SCIMTokenToggleArea
(
'
.js-generate-scim-token-container
'
,
'
.js-scim-token-container
'
,
groupPath
,
);
new
SamlSettingsForm
(
'
#js-saml-settings-form
'
).
init
();
new
SamlSettingsForm
(
'
#js-saml-settings-form
'
).
init
();
}
}
ee/app/assets/javascripts/saml_sso/components/scim_token.vue
0 → 100644
View file @
d7e43926
<
script
>
import
{
GlSprintf
,
GlButton
,
GlLoadingIcon
,
GlModal
}
from
'
@gitlab/ui
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
createFlash
from
'
~/flash
'
;
import
InputCopyToggleVisibility
from
'
~/vue_shared/components/form/input_copy_toggle_visibility.vue
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
export
default
{
components
:
{
GlSprintf
,
InputCopyToggleVisibility
,
GlButton
,
GlLoadingIcon
,
GlModal
,
},
i18n
:
{
copyToken
:
s__
(
'
GroupSaml|Copy SCIM token
'
),
copyEndpointUrl
:
s__
(
'
GroupSaml|Copy SCIM API endpoint URL
'
),
tokenLabel
:
s__
(
'
GroupSaml|Your SCIM token
'
),
endpointUrlLabel
:
s__
(
'
GroupSaml|SCIM API endpoint URL
'
),
generateTokenButtonText
:
s__
(
'
GroupSAML|Generate a SCIM token
'
),
tokenHasNotBeenGeneratedMessage
:
s__
(
'
GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management.
'
,
),
tokenNeedsToBeResetDescription
:
s__
(
'
GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to %{linkStart}reset it%{linkEnd}.
'
,
),
tokenHasBeenGeneratedOrResetDescription
:
s__
(
"
GroupSAML|Make sure you save this token — you won't be able to access it again.
"
,
),
generateTokenErrorMessage
:
s__
(
'
GroupSAML|An error occurred generating your SCIM token. Please try again.
'
,
),
resetTokenErrorMessage
:
s__
(
'
GroupSAML|An error occurred resetting your SCIM token. Please try again.
'
,
),
modal
:
{
title
:
__
(
'
Are you sure?
'
),
body
:
s__
(
'
GroupSAML|Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated.
'
,
),
},
},
modal
:
{
id
:
'
reset-scim-token-modal
'
,
actionPrimary
:
{
text
:
s__
(
'
GroupSAML|Reset SCIM token
'
),
attributes
:
{
variant
:
'
danger
'
,
},
},
actionSecondary
:
{
text
:
__
(
'
Cancel
'
),
attributes
:
{
variant
:
'
default
'
,
},
},
},
tokenInputId
:
'
scim_token
'
,
endpointUrlInputId
:
'
scim_token_endpoint_url
'
,
inject
:
[
'
initialEndpointUrl
'
,
'
generateTokenPath
'
],
data
()
{
return
{
loading
:
false
,
token
:
''
,
endpointUrl
:
this
.
initialEndpointUrl
,
modalVisible
:
false
,
};
},
computed
:
{
tokenHasNotBeenGenerated
()
{
return
!
this
.
endpointUrl
&&
this
.
token
===
''
;
},
tokenNeedsToBeReset
()
{
return
this
.
endpointUrl
&&
this
.
token
===
''
;
},
tokenInputValue
()
{
return
this
.
tokenNeedsToBeReset
?
'
*
'
.
repeat
(
20
)
:
this
.
token
;
},
tokenFormInputGroupProps
()
{
return
{
id
:
this
.
$options
.
tokenInputId
,
class
:
'
gl-form-input-xl
'
};
},
tokenEndpointUrlFormInputGroupProps
()
{
return
{
id
:
this
.
$options
.
endpointUrlInputId
,
class
:
'
gl-form-input-xl
'
};
},
contentContainerClasses
()
{
return
{
'
gl-visibility-hidden
'
:
this
.
loading
};
},
},
methods
:
{
async
callApi
(
errorMessage
)
{
this
.
loading
=
true
;
try
{
const
{
data
:
{
scim_api_url
:
endpointUrl
,
scim_token
:
token
},
}
=
await
axios
.
post
(
this
.
generateTokenPath
);
this
.
token
=
token
;
this
.
endpointUrl
=
endpointUrl
;
}
catch
(
error
)
{
createFlash
({
message
:
errorMessage
,
captureError
:
true
,
error
,
});
}
finally
{
this
.
loading
=
false
;
}
},
handleGenerateTokenButtonClick
()
{
this
.
callApi
(
this
.
$options
.
i18n
.
generateTokenErrorMessage
);
},
handleModalPrimary
()
{
this
.
callApi
(
this
.
$options
.
i18n
.
resetTokenErrorMessage
);
},
handleResetButtonClick
()
{
this
.
modalVisible
=
true
;
},
},
};
</
script
>
<
template
>
<div
class=
"gl-mt-5 gl-relative"
>
<gl-modal
v-model=
"modalVisible"
:modal-id=
"$options.modal.id"
:title=
"$options.i18n.modal.title"
:action-primary=
"$options.modal.actionPrimary"
:action-secondary=
"$options.modal.actionSecondary"
size=
"sm"
@
primary=
"handleModalPrimary"
>
{{
$options
.
i18n
.
modal
.
body
}}
</gl-modal>
<div
v-if=
"loading"
class=
"gl-absolute gl-top-5 gl-left-0 gl-right-0 gl-display-flex gl-justify-content-center"
>
<gl-loading-icon
size=
"md"
/>
</div>
<div
v-if=
"tokenHasNotBeenGenerated"
:class=
"contentContainerClasses"
data-testid=
"content-container"
>
<p>
{{
$options
.
i18n
.
tokenHasNotBeenGeneratedMessage
}}
</p>
<gl-button
@
click=
"handleGenerateTokenButtonClick"
>
{{
$options
.
i18n
.
generateTokenButtonText
}}
</gl-button>
</div>
<div
v-else
:class=
"contentContainerClasses"
data-testid=
"content-container"
>
<input-copy-toggle-visibility
:label=
"$options.i18n.tokenLabel"
:label-for=
"$options.tokenInputId"
:form-input-group-props=
"tokenFormInputGroupProps"
:value=
"tokenInputValue"
:copy-button-title=
"$options.i18n.copyToken"
:show-toggle-visibility-button=
"!tokenNeedsToBeReset"
:show-copy-button=
"!tokenNeedsToBeReset"
>
<template
#description
>
<gl-sprintf
v-if=
"tokenNeedsToBeReset"
:message=
"$options.i18n.tokenNeedsToBeResetDescription"
>
<template
#link
="
{ content }">
<gl-button
variant=
"link"
@
click=
"handleResetButtonClick"
>
{{
content
}}
</gl-button>
</
template
>
</gl-sprintf>
<
template
v-else
>
{{
$options
.
i18n
.
tokenHasBeenGeneratedOrResetDescription
}}
</
template
>
</template>
</input-copy-toggle-visibility>
<input-copy-toggle-visibility
:label=
"$options.i18n.endpointUrlLabel"
:label-for=
"$options.endpointUrlInputId"
:form-input-group-props=
"tokenEndpointUrlFormInputGroupProps"
:value=
"endpointUrl"
:copy-button-title=
"$options.i18n.copyEndpointUrl"
:show-toggle-visibility-button=
"false"
/>
</div>
</div>
</template>
ee/app/assets/javascripts/saml_sso/index.js
View file @
d7e43926
import
Vue
from
'
vue
'
;
import
SCIMTokenToggleArea
from
'
ee/saml_providers/scim_token_toggle_area
'
;
import
ScimToken
from
'
./components/scim_token.vue
'
;
import
{
AUTO_REDIRECT_TO_PROVIDER_BUTTON_SELECTOR
}
from
'
./constants
'
;
import
{
AUTO_REDIRECT_TO_PROVIDER_BUTTON_SELECTOR
}
from
'
./constants
'
;
export
const
redirectUserWithSSOIdentity
=
()
=>
{
export
const
redirectUserWithSSOIdentity
=
()
=>
{
...
@@ -9,3 +14,34 @@ export const redirectUserWithSSOIdentity = () => {
...
@@ -9,3 +14,34 @@ export const redirectUserWithSSOIdentity = () => {
signInButton
.
click
();
signInButton
.
click
();
};
};
export
const
initScimTokenApp
=
()
=>
{
const
el
=
document
.
getElementById
(
'
js-scim-token-app
'
);
if
(
!
el
)
{
// `scim_token_vue` feature flag is disabled, load legacy JS.
const
groupPath
=
document
.
querySelector
(
'
#issuer
'
).
value
;
// eslint-disable-next-line no-new
new
SCIMTokenToggleArea
(
'
.js-generate-scim-token-container
'
,
'
.js-scim-token-container
'
,
groupPath
,
);
return
false
;
}
const
{
endpointUrl
,
generateTokenPath
}
=
el
.
dataset
;
return
new
Vue
({
el
,
provide
:
{
initialEndpointUrl
:
endpointUrl
,
generateTokenPath
,
},
render
(
createElement
)
{
return
createElement
(
ScimToken
);
},
});
};
ee/app/views/groups/saml_providers/_scim_token.html.haml
View file @
d7e43926
-
scim_token_not_present
=
@scim_token_url
.
nil?
-
if
Feature
.
enabled?
(
:scim_token_vue
,
default_enabled: :yaml
)
.gl-mt-3.js-generate-scim-token-container
{
class:
"#{ 'd-none' unless scim_token_not_present}"
}
#js-scim-token-app
{
data:
{
endpoint_url:
@scim_token_url
,
generate_token_path:
group_scim_oauth_path
}
}
%p
=
s_
(
'GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management.'
)
-
else
%button
.btn.gl-button.js-generate-scim-token
{
type:
'button'
}
-
scim_token_not_present
=
@scim_token_url
.
nil?
=
s_
(
'GroupSAML|Generate a SCIM token'
)
.gl-mt-3.js-generate-scim-token-container
{
class:
"#{ 'd-none' unless scim_token_not_present}"
}
.gl-mt-3.js-scim-token-container
{
class:
"#{ 'd-none' if scim_token_not_present}"
}
%p
=
s_
(
'GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management.'
)
.well-segment.borderless.mb-3.col-12.col-lg-9.p-0
%button
.btn.gl-button.js-generate-scim-token
{
type:
'button'
}
=
render
'scim_row'
,
value:
'********************'
,
field:
'scim_token'
,
label_text:
s_
(
'GroupSAML|Your SCIM token'
),
show_clipboard:
false
=
s_
(
'GroupSAML|Generate a SCIM token'
)
.form-text.text-muted.js-scim-token-helper-text
.gl-mt-3.js-scim-token-container
{
class:
"#{ 'd-none' if scim_token_not_present}"
}
%span
.well-segment.borderless.mb-3.col-12.col-lg-9.p-0
=
s_
(
'GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to '
)
=
render
'scim_row'
,
value:
'********************'
,
field:
'scim_token'
,
label_text:
s_
(
'GroupSAML|Your SCIM token'
),
show_clipboard:
false
%button
.btn.gl-button.btn-confirm.btn-link.d-inline.align-baseline.js-reset-scim-token
{
type:
'button'
}
.form-text.text-muted.js-scim-token-helper-text
=
_
(
'reset it.'
)
%span
%span
.d-none
=
s_
(
'GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to '
)
=
s_
(
"GroupSAML|Make sure you save this token — you won't be able to access it again."
)
%button
.btn.gl-button.btn-confirm.btn-link.d-inline.align-baseline.js-reset-scim-token
{
type:
'button'
}
.well-segment.borderless.col-12.col-lg-9.p-0
=
_
(
'reset it.'
)
=
render
'scim_row'
,
value:
@scim_token_url
,
field:
'scim_endpoint_url'
,
label_text:
s_
(
'GroupSAML|SCIM API endpoint URL'
),
show_clipboard:
true
%span
.d-none
.gl-mt-3.text-center.js-scim-loading-container.d-none
=
s_
(
"GroupSAML|Make sure you save this token — you won't be able to access it again."
)
.gl-spinner
.well-segment.borderless.col-12.col-lg-9.p-0
=
render
'scim_row'
,
value:
@scim_token_url
,
field:
'scim_endpoint_url'
,
label_text:
s_
(
'GroupSAML|SCIM API endpoint URL'
),
show_clipboard:
true
.gl-mt-3.text-center.js-scim-loading-container.d-none
.gl-spinner
ee/spec/features/groups/scim_token_spec.rb
View file @
d7e43926
...
@@ -3,47 +3,114 @@
...
@@ -3,47 +3,114 @@
require
'spec_helper'
require
'spec_helper'
RSpec
.
describe
'SCIM Token handling'
,
:js
do
RSpec
.
describe
'SCIM Token handling'
,
:js
do
let
(
:user
)
{
create
(
:user
)
}
include
Spec
::
Support
::
Helpers
::
ModalHelpers
let
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:group
)
{
create
(
:group
)
}
before
do
before
do
group
.
add_owner
(
user
)
group
.
add_owner
(
user
)
stub_licensed_features
(
group_saml:
true
)
stub_licensed_features
(
group_saml:
true
)
end
end
describe
'group has no existing scim token'
do
context
'when `scim_token_vue` feature flag is disabled'
do
before
do
stub_feature_flags
(
scim_token_vue:
false
)
end
describe
'group has no existing scim token'
do
before
do
sign_in
(
user
)
visit
group_saml_providers_path
(
group
)
end
it
'displays generate token form'
do
expect
(
page
).
to
have_selector
(
'.js-generate-scim-token-container'
,
visible:
true
)
expect
(
page
).
to
have_selector
(
'.js-scim-token-container'
,
visible:
false
)
page
.
within
'.js-generate-scim-token-container'
do
expect
(
page
).
to
have_content
(
'Generate a SCIM token to set up your System for Cross-Domain Identity Management.'
)
expect
(
page
).
to
have_button
(
'Generate a SCIM token'
)
end
end
end
describe
'group has existing scim token'
do
let!
(
:scim_token
)
{
create
(
:scim_oauth_access_token
,
group:
group
)
}
before
do
sign_in
(
user
)
visit
group_saml_providers_path
(
group
)
end
it
'displays the scim form with an obfuscated token'
do
expect
(
page
).
to
have_selector
(
'.js-generate-scim-token-container'
,
visible:
false
)
expect
(
page
).
to
have_selector
(
'.js-scim-token-container'
,
visible:
true
)
page
.
within
'.js-scim-token-container'
do
expect
(
page
).
to
have_button
(
'reset it.'
)
expect
(
page
.
find
(
'#scim_token'
).
value
).
to
eq
(
'********************'
)
expect
(
page
.
find
(
'#scim_endpoint_url'
).
value
).
to
eq
(
scim_token
.
as_entity_json
[
:scim_api_url
])
end
end
end
end
def
find_token_field
page
.
find_field
(
'Your SCIM token'
)
end
def
find_api_endpoint_url_field
page
.
find_field
(
'SCIM API endpoint URL'
)
end
context
'when group has no existing SCIM token'
do
before
do
before
do
sign_in
(
user
)
sign_in
(
user
)
visit
group_saml_providers_path
(
group
)
visit
group_saml_providers_path
(
group
)
end
end
it
'displays generate token form'
do
it
'displays generate token form'
do
expect
(
page
).
to
have_selector
(
'.js-generate-scim-token-container'
,
visible:
true
)
expect
(
page
).
to
have_content
(
'Generate a SCIM token to set up your System for Cross-Domain Identity Management.'
)
expect
(
page
).
to
have_selector
(
'.js-scim-token-container'
,
visible:
false
)
expect
(
page
).
to
have_button
(
'Generate a SCIM token'
)
page
.
within
'.js-generate-scim-token-container'
do
expect
(
page
).
to
have_content
(
'Generate a SCIM token to set up your System for Cross-Domain Identity Management.'
)
expect
(
page
).
to
have_button
(
'Generate a SCIM token'
)
end
end
end
end
end
describe
'group has existing scim
token'
do
context
'when group has existing SCIM
token'
do
let
!
(
:scim_token
)
{
create
(
:scim_oauth_access_token
,
group:
group
)
}
let
_it_be
(
:scim_token
)
{
create
(
:scim_oauth_access_token
,
group:
group
)
}
before
do
before
do
sign_in
(
user
)
sign_in
(
user
)
visit
group_saml_providers_path
(
group
)
visit
group_saml_providers_path
(
group
)
end
end
it
'displays the scim form with an obfuscated token'
do
it
'displays the SCIM form with an obfuscated token'
do
expect
(
page
).
to
have_selector
(
'.js-generate-scim-token-container'
,
visible:
false
)
expect
(
page
).
to
have_button
(
'reset it'
)
expect
(
page
).
to
have_selector
(
'.js-scim-token-container'
,
visible:
true
)
expect
(
find_token_field
.
value
).
to
eq
(
'********************'
)
expect
(
page
).
not_to
have_button
(
'Click to reveal'
)
expect
(
page
).
not_to
have_button
(
'Copy SCIM token'
)
expect
(
find_api_endpoint_url_field
.
value
).
to
eq
(
scim_token
.
as_entity_json
[
:scim_api_url
])
end
context
'when `reset it` button is clicked'
do
before
do
accept_gl_confirm
(
'Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated.'
,
button_text:
'Reset SCIM token'
)
do
page
.
click_button
(
'reset it'
)
end
end
it
'displays the SCIM form with an obfuscated token that can be copied or shown'
do
expect
(
find_api_endpoint_url_field
.
value
).
to
eq
(
scim_token
.
as_entity_json
[
:scim_api_url
])
expect
(
page
).
to
have_button
(
'Copy SCIM token'
)
page
.
within
'.js-scim-token-container'
do
expect
(
find_token_field
.
value
).
to
eq
(
'********************'
)
expect
(
page
).
to
have_button
(
'reset it.
'
)
page
.
click_button
(
'Click to reveal
'
)
expect
(
page
.
find
(
'#scim_token'
).
value
).
to
eq
(
'********************'
)
expect
(
find_token_field
.
value
).
not_
to
eq
(
'********************'
)
expect
(
page
.
find
(
'#scim_endpoint_url'
).
value
).
to
eq
(
scim_token
.
as_entity_json
[
:scim_api_url
]
)
expect
(
find_token_field
.
value
).
not_to
eq
(
''
)
end
end
end
end
end
end
...
...
ee/spec/frontend/fixtures/saml_providers.rb
View file @
d7e43926
...
@@ -14,6 +14,7 @@ RSpec.describe Groups::SamlProvidersController, '(JavaScript fixtures)', type: :
...
@@ -14,6 +14,7 @@ RSpec.describe Groups::SamlProvidersController, '(JavaScript fixtures)', type: :
group
.
add_owner
(
user
)
group
.
add_owner
(
user
)
allow
(
Devise
).
to
receive
(
:omniauth_providers
).
and_return
(
%i(group_saml)
)
allow
(
Devise
).
to
receive
(
:omniauth_providers
).
and_return
(
%i(group_saml)
)
stub_licensed_features
(
group_saml:
true
)
stub_licensed_features
(
group_saml:
true
)
stub_feature_flags
(
scim_token_vue:
false
)
end
end
it
'groups/saml_providers/show.html'
do
it
'groups/saml_providers/show.html'
do
...
...
ee/spec/frontend/saml_sso/components/scim_token_spec.js
0 → 100644
View file @
d7e43926
import
{
merge
,
pickBy
,
isUndefined
}
from
'
lodash
'
;
import
AxiosMockAdapter
from
'
axios-mock-adapter
'
;
import
{
GlLoadingIcon
,
GlModal
}
from
'
@gitlab/ui
'
;
import
{
mountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
createFlash
from
'
~/flash
'
;
import
ScimToken
from
'
ee/saml_sso/components/scim_token.vue
'
;
import
InputCopyToggleVisibility
from
'
~/vue_shared/components/form/input_copy_toggle_visibility.vue
'
;
jest
.
mock
(
'
~/flash
'
);
describe
(
'
ScimToken
'
,
()
=>
{
let
wrapper
;
let
axiosMock
;
const
defaultProvide
=
{
initialEndpointUrl
:
undefined
,
generateTokenPath
:
'
/groups/saml-test/-/scim_oauth
'
,
};
const
mockApiResponse
=
{
scim_api_url
:
'
https://foo.bar/api/scim/v2/groups/saml-test
'
,
scim_token
:
'
quL51_RR49CcHpjxJN_S
'
,
};
const
createComponent
=
(
options
=
{})
=>
{
wrapper
=
mountExtended
(
ScimToken
,
merge
(
{},
{
provide
:
defaultProvide
,
},
options
,
),
);
};
const
findGenerateTokenButton
=
()
=>
wrapper
.
findByRole
(
'
button
'
,
{
name
:
ScimToken
.
i18n
.
generateTokenButtonText
});
const
findResetItButton
=
()
=>
wrapper
.
findByRole
(
'
button
'
,
{
name
:
'
reset it
'
});
const
resetAndConfirm
=
async
()
=>
{
await
findResetItButton
().
trigger
(
'
click
'
);
wrapper
.
findComponent
(
GlModal
).
vm
.
$emit
(
'
primary
'
);
};
const
expectLoadingIconExists
=
()
=>
{
expect
(
wrapper
.
findComponent
(
GlLoadingIcon
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
findByTestId
(
'
content-container
'
).
classes
()).
toContain
(
'
gl-visibility-hidden
'
);
};
const
expectInputRenderedWithProps
=
(
input
,
props
)
=>
{
const
{
formInputGroupProps
,
value
,
copyButtonTitle
,
showToggleVisibilityButton
,
showCopyButton
,
label
,
labelFor
,
}
=
props
;
expect
(
input
.
props
()).
toMatchObject
(
pickBy
(
{
formInputGroupProps
,
value
,
copyButtonTitle
,
showToggleVisibilityButton
,
showCopyButton
,
},
!
isUndefined
,
),
);
expect
(
input
.
attributes
()).
toMatchObject
({
label
,
'
label-for
'
:
labelFor
,
});
};
const
itShowsLoadingIconThenDisplaysInputs
=
()
=>
{
it
(
'
shows loading icon then displays token in hidden state and SCIM API endpoint URL
'
,
async
()
=>
{
expectLoadingIconExists
();
await
waitForPromises
();
const
[
tokenInput
,
apiEndpointInput
]
=
wrapper
.
findAllComponents
(
InputCopyToggleVisibility
,
).
wrappers
;
expectInputRenderedWithProps
(
tokenInput
,
{
formInputGroupProps
:
{
id
:
ScimToken
.
tokenInputId
,
class
:
'
gl-form-input-xl
'
},
value
:
mockApiResponse
.
scim_token
,
copyButtonTitle
:
ScimToken
.
i18n
.
copyToken
,
showToggleVisibilityButton
:
true
,
showCopyButton
:
true
,
label
:
ScimToken
.
i18n
.
tokenLabel
,
labelFor
:
ScimToken
.
tokenInputId
,
});
expect
(
wrapper
.
findByText
(
ScimToken
.
i18n
.
tokenHasBeenGeneratedOrResetDescription
).
exists
(),
).
toBe
(
true
);
expectInputRenderedWithProps
(
apiEndpointInput
,
{
formInputGroupProps
:
{
id
:
ScimToken
.
endpointUrlInputId
,
class
:
'
gl-form-input-xl
'
},
value
:
mockApiResponse
.
scim_api_url
,
copyButtonTitle
:
ScimToken
.
i18n
.
copyEndpointUrl
,
showToggleVisibilityButton
:
false
,
label
:
ScimToken
.
i18n
.
endpointUrlLabel
,
labelFor
:
ScimToken
.
endpointUrlInputId
,
});
});
};
const
itShowsLoadingIconThenCallsCreateFlash
=
(
expectedErrorMessage
)
=>
{
it
(
'
shows loading icon then calls `createFlash`
'
,
async
()
=>
{
expectLoadingIconExists
();
await
waitForPromises
();
expect
(
createFlash
).
toHaveBeenCalledWith
({
message
:
expectedErrorMessage
,
captureError
:
true
,
error
:
expect
.
any
(
Error
),
});
});
};
beforeEach
(()
=>
{
axiosMock
=
new
AxiosMockAdapter
(
axios
);
});
afterEach
(()
=>
{
wrapper
.
destroy
();
axiosMock
.
restore
();
});
describe
(
'
when token has not been generated
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
displays message and button to generate token
'
,
()
=>
{
expect
(
wrapper
.
findByText
(
ScimToken
.
i18n
.
tokenHasNotBeenGeneratedMessage
).
exists
()).
toBe
(
true
,
);
expect
(
findGenerateTokenButton
().
exists
()).
toBe
(
true
);
});
describe
(
`when \`
${
ScimToken
.
i18n
.
generateTokenButtonText
}
\` button is clicked`
,
()
=>
{
describe
(
'
when API request is successful
'
,
()
=>
{
beforeEach
(
async
()
=>
{
axiosMock
.
onPost
(
defaultProvide
.
generateTokenPath
).
reply
(
200
,
mockApiResponse
);
await
findGenerateTokenButton
().
trigger
(
'
click
'
);
});
itShowsLoadingIconThenDisplaysInputs
();
});
describe
(
'
when API request is not successful
'
,
()
=>
{
beforeEach
(
async
()
=>
{
axiosMock
.
onPost
(
defaultProvide
.
generateTokenPath
).
networkError
();
await
findGenerateTokenButton
().
trigger
(
'
click
'
);
});
itShowsLoadingIconThenCallsCreateFlash
(
ScimToken
.
i18n
.
generateTokenErrorMessage
);
});
});
});
describe
(
'
when token has been generated but needs to be reset
'
,
()
=>
{
const
initialEndpointUrl
=
'
https://foo.bar/api/scim/v2/groups/saml-test
'
;
beforeEach
(()
=>
{
createComponent
({
provide
:
{
initialEndpointUrl
,
},
});
});
it
(
'
displays message and reset link
'
,
()
=>
{
expect
(
wrapper
.
findByText
(
'
The SCIM token is now hidden. To see the value of the token again, you need to
'
,
{
exact
:
false
},
)
.
exists
(),
).
toBe
(
true
);
expect
(
wrapper
.
findByRole
(
'
button
'
,
{
name
:
'
reset it
'
}).
exists
()).
toBe
(
true
);
});
it
(
'
displays hidden token and SCIM API endpoint URL
'
,
()
=>
{
const
[
tokenInput
,
apiEndpointInput
]
=
wrapper
.
findAllComponents
(
InputCopyToggleVisibility
,
).
wrappers
;
expectInputRenderedWithProps
(
tokenInput
,
{
formInputGroupProps
:
{
id
:
ScimToken
.
tokenInputId
,
class
:
'
gl-form-input-xl
'
},
value
:
'
********************
'
,
showToggleVisibilityButton
:
false
,
showCopyButton
:
false
,
label
:
ScimToken
.
i18n
.
tokenLabel
,
labelFor
:
ScimToken
.
tokenInputId
,
});
expectInputRenderedWithProps
(
apiEndpointInput
,
{
formInputGroupProps
:
{
id
:
ScimToken
.
endpointUrlInputId
,
class
:
'
gl-form-input-xl
'
},
value
:
initialEndpointUrl
,
copyButtonTitle
:
ScimToken
.
i18n
.
copyEndpointUrl
,
showToggleVisibilityButton
:
false
,
label
:
ScimToken
.
i18n
.
endpointUrlLabel
,
labelFor
:
ScimToken
.
endpointUrlInputId
,
});
});
describe
(
'
when `reset it` button is clicked
'
,
()
=>
{
describe
(
'
when API request is successful
'
,
()
=>
{
beforeEach
(
async
()
=>
{
axiosMock
.
onPost
(
defaultProvide
.
generateTokenPath
).
reply
(
200
,
mockApiResponse
);
resetAndConfirm
();
});
itShowsLoadingIconThenDisplaysInputs
();
});
describe
(
'
when API request is not successful
'
,
()
=>
{
beforeEach
(
async
()
=>
{
axiosMock
.
onPost
(
defaultProvide
.
initialEndpointUrl
).
networkError
();
resetAndConfirm
();
});
itShowsLoadingIconThenCallsCreateFlash
(
ScimToken
.
i18n
.
resetTokenErrorMessage
);
});
});
});
});
locale/gitlab.pot
View file @
d7e43926
...
@@ -16725,9 +16725,18 @@ msgstr ""
...
@@ -16725,9 +16725,18 @@ msgstr ""
msgid "GroupSAML|Active SAML Group Links (%{count})"
msgid "GroupSAML|Active SAML Group Links (%{count})"
msgstr ""
msgstr ""
msgid "GroupSAML|An error occurred generating your SCIM token. Please try again."
msgstr ""
msgid "GroupSAML|An error occurred resetting your SCIM token. Please try again."
msgstr ""
msgid "GroupSAML|Are you sure you want to remove the SAML group link?"
msgid "GroupSAML|Are you sure you want to remove the SAML group link?"
msgstr ""
msgstr ""
msgid "GroupSAML|Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
msgstr ""
msgid "GroupSAML|Before enforcing SSO, enable SAML authentication."
msgid "GroupSAML|Before enforcing SSO, enable SAML authentication."
msgstr ""
msgstr ""
...
@@ -16800,6 +16809,9 @@ msgstr ""
...
@@ -16800,6 +16809,9 @@ msgstr ""
msgid "GroupSAML|Prohibit outer forks for this group"
msgid "GroupSAML|Prohibit outer forks for this group"
msgstr ""
msgstr ""
msgid "GroupSAML|Reset SCIM token"
msgstr ""
msgid "GroupSAML|Role to assign members of this SAML group."
msgid "GroupSAML|Role to assign members of this SAML group."
msgstr ""
msgstr ""
...
@@ -16839,6 +16851,9 @@ msgstr ""
...
@@ -16839,6 +16851,9 @@ msgstr ""
msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
msgstr ""
msgstr ""
msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to %{linkStart}reset it%{linkEnd}."
msgstr ""
msgid "GroupSAML|The case-sensitive group name that will be sent by the SAML identity provider."
msgid "GroupSAML|The case-sensitive group name that will be sent by the SAML identity provider."
msgstr ""
msgstr ""
...
@@ -16872,6 +16887,18 @@ msgstr ""
...
@@ -16872,6 +16887,18 @@ msgstr ""
msgid "GroupSAML|recommend persistent ID instead of email"
msgid "GroupSAML|recommend persistent ID instead of email"
msgstr ""
msgstr ""
msgid "GroupSaml|Copy SCIM API endpoint URL"
msgstr ""
msgid "GroupSaml|Copy SCIM token"
msgstr ""
msgid "GroupSaml|SCIM API endpoint URL"
msgstr ""
msgid "GroupSaml|Your SCIM token"
msgstr ""
msgid "GroupSelect|No matching results"
msgid "GroupSelect|No matching results"
msgstr ""
msgstr ""
...
...
spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js
View file @
d7e43926
...
@@ -217,4 +217,15 @@ describe('InputCopyToggleVisibility', () => {
...
@@ -217,4 +217,15 @@ describe('InputCopyToggleVisibility', () => {
expect
(
findCopyButton
().
props
(
'
title
'
)).
toBe
(
'
Copy token
'
);
expect
(
findCopyButton
().
props
(
'
title
'
)).
toBe
(
'
Copy token
'
);
});
});
it
(
'
renders slots in `gl-form-group`
'
,
()
=>
{
const
description
=
'
Mock input description
'
;
createComponent
({
slots
:
{
description
,
},
});
expect
(
wrapper
.
findByText
(
description
).
exists
()).
toBe
(
true
);
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment