Skip to content

Commit e40df4d

Browse files
Pearl1594yadvr
andcommitted
login: add SAML single-sign-on support (#169)
This adds SAML single-sign-on support. Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> Co-authored-by: Rohit Yadav <rohit@apache.org>
1 parent e03c332 commit e40df4d

3 files changed

Lines changed: 64 additions & 27 deletions

File tree

ui/src/permission.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import store from './store'
2222

2323
import NProgress from 'nprogress' // progress bar
2424
import 'nprogress/nprogress.css' // progress bar style
25+
import message from 'ant-design-vue/es/message'
2526
import notification from 'ant-design-vue/es/notification'
2627
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
2728
import { ACCESS_TOKEN } from '@/store/mutation-types'
@@ -41,6 +42,7 @@ router.beforeEach((to, from, next) => {
4142
NProgress.done()
4243
} else {
4344
if (Object.keys(store.getters.apis).length === 0) {
45+
message.loading('Discovering features...', 5)
4446
store
4547
.dispatch('GetInfo')
4648
.then(apis => {

ui/src/utils/request.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,18 @@ const service = axios.create({
2929
})
3030

3131
const err = (error) => {
32-
if (error.response) {
33-
console.log('error has occurred')
34-
console.log(error)
32+
const response = error.response
33+
if (response) {
34+
console.log(response)
3535
const token = Vue.ls.get(ACCESS_TOKEN)
36-
if (error.response.status === 403) {
37-
const data = error.response.data
36+
if (response.status === 403) {
37+
const data = response.data
3838
notification.error({ message: 'Forbidden', description: data.message })
3939
}
40-
if (error.response.status === 401) {
40+
if (response.status === 401) {
41+
if (response.config && response.config.params && ['listIdps'].includes(response.config.params.command)) {
42+
return
43+
}
4144
notification.error({ message: 'Unauthorized', description: 'Authorization verification failed' })
4245
if (token) {
4346
store.dispatch('Logout').then(() => {
@@ -47,7 +50,7 @@ const err = (error) => {
4750
})
4851
}
4952
}
50-
if (error.response.status === 404) {
53+
if (response.status === 404) {
5154
notification.error({ message: 'Not Found', description: 'Resource not found' })
5255
this.$router.push({ path: '/exception/404' })
5356
}

ui/src/views/auth/Login.vue

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@
2828
size="large"
2929
:tabBarStyle="{ textAlign: 'center', borderBottom: 'unset' }"
3030
@change="handleTabClick"
31+
:animated="false"
3132
>
32-
<a-tab-pane key="tab1">
33+
<a-tab-pane key="cs">
3334
<span slot="tab">
3435
<a-icon type="safety" />
35-
<b>CloudStack Login</b>
36+
Portal Login
3637
</span>
3738
<a-form-item>
3839
<a-input
@@ -78,11 +79,18 @@
7879
</a-form-item>
7980

8081
</a-tab-pane>
81-
<a-tab-pane key="tab2" disabled>
82+
<a-tab-pane key="saml" :disabled="idps.length === 0">
8283
<span slot="tab">
8384
<a-icon type="audit" />
84-
<b>SAML</b>
85+
Single-Sign-On
8586
</span>
87+
<a-form-item>
88+
<a-select v-decorator="['idp', { initialValue: selectedIdp } ]">
89+
<a-select-option v-for="(idp, idx) in idps" :key="idx" :value="idp.id">
90+
{{ idp.orgName }}
91+
</a-select-option>
92+
</a-select>
93+
</a-form-item>
8694
</a-tab-pane>
8795
</a-tabs>
8896

@@ -100,14 +108,18 @@
100108
</template>
101109

102110
<script>
111+
import { api } from '@/api'
103112
import { mapActions } from 'vuex'
113+
import config from '@/config/settings'
104114
105115
export default {
106116
components: {
107117
},
108118
data () {
109119
return {
110-
customActiveKey: 'tab1',
120+
idps: [],
121+
selectedIdp: '',
122+
customActiveKey: 'cs',
111123
loginBtn: false,
112124
loginType: 0,
113125
form: this.$form.createForm(this),
@@ -120,8 +132,19 @@ export default {
120132
},
121133
created () {
122134
},
135+
mounted () {
136+
this.fetchData()
137+
},
123138
methods: {
124139
...mapActions(['Login', 'Logout']),
140+
fetchData () {
141+
api('listIdps').then(response => {
142+
if (response) {
143+
this.idps = response.listidpsresponse.idp || []
144+
this.selectedIdp = this.idps[0].id || ''
145+
}
146+
})
147+
},
125148
// handler
126149
handleUsernameOrEmail (rule, value, callback) {
127150
const { state } = this
@@ -148,24 +171,33 @@ export default {
148171
149172
state.loginBtn = true
150173
151-
const validateFieldsKey = customActiveKey === 'tab1' ? ['username', 'password', 'domain'] : ['mobile', 'captcha']
174+
const validateFieldsKey = customActiveKey === 'cs' ? ['username', 'password', 'domain'] : ['idp']
152175
153176
validateFields(validateFieldsKey, { force: true }, (err, values) => {
154177
if (!err) {
155-
const loginParams = { ...values }
156-
delete loginParams.username
157-
loginParams[!state.loginType ? 'email' : 'username'] = values.username
158-
loginParams.password = values.password
159-
loginParams.domain = values.domain
160-
if (!loginParams.domain) {
161-
loginParams.domain = '/'
178+
if (customActiveKey === 'cs') {
179+
const loginParams = { ...values }
180+
delete loginParams.username
181+
loginParams[!state.loginType ? 'email' : 'username'] = values.username
182+
loginParams.password = values.password
183+
loginParams.domain = values.domain
184+
if (!loginParams.domain) {
185+
loginParams.domain = '/'
186+
}
187+
Login(loginParams)
188+
.then((res) => this.loginSuccess(res))
189+
.catch(err => this.requestFailed(err))
190+
.finally(() => {
191+
state.loginBtn = false
192+
})
193+
} else if (customActiveKey === 'saml') {
194+
state.loginBtn = false
195+
var samlUrl = config.apiBase + '?command=samlSso'
196+
if (values.idp) {
197+
samlUrl += ('&idpid=' + values.idp)
198+
}
199+
window.location.href = samlUrl
162200
}
163-
Login(loginParams)
164-
.then((res) => this.loginSuccess(res))
165-
.catch(err => this.requestFailed(err))
166-
.finally(() => {
167-
state.loginBtn = false
168-
})
169201
} else {
170202
setTimeout(() => {
171203
state.loginBtn = false
@@ -174,7 +206,6 @@ export default {
174206
})
175207
},
176208
loginSuccess (res) {
177-
this.$message.loading('Login Successful. Discovering Features...', 5)
178209
this.$router.push({ path: '/dashboard' }).catch(() => {})
179210
},
180211
requestFailed (err) {
@@ -204,6 +235,7 @@ export default {
204235
}
205236
206237
button.login-button {
238+
margin-top: 8px;
207239
padding: 0 15px;
208240
font-size: 16px;
209241
height: 40px;

0 commit comments

Comments
 (0)