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>
This commit is contained in:
Pearl Dsilva 2020-02-19 17:52:46 +05:30 committed by Rohit Yadav
parent e03c332d56
commit e40df4d25d
3 changed files with 64 additions and 27 deletions

View File

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

View File

@ -29,15 +29,18 @@ const service = axios.create({
})
const err = (error) => {
if (error.response) {
console.log('error has occurred')
console.log(error)
const response = error.response
if (response) {
console.log(response)
const token = Vue.ls.get(ACCESS_TOKEN)
if (error.response.status === 403) {
const data = error.response.data
if (response.status === 403) {
const data = response.data
notification.error({ message: 'Forbidden', description: data.message })
}
if (error.response.status === 401) {
if (response.status === 401) {
if (response.config && response.config.params && ['listIdps'].includes(response.config.params.command)) {
return
}
notification.error({ message: 'Unauthorized', description: 'Authorization verification failed' })
if (token) {
store.dispatch('Logout').then(() => {
@ -47,7 +50,7 @@ const err = (error) => {
})
}
}
if (error.response.status === 404) {
if (response.status === 404) {
notification.error({ message: 'Not Found', description: 'Resource not found' })
this.$router.push({ path: '/exception/404' })
}

View File

@ -28,11 +28,12 @@
size="large"
:tabBarStyle="{ textAlign: 'center', borderBottom: 'unset' }"
@change="handleTabClick"
:animated="false"
>
<a-tab-pane key="tab1">
<a-tab-pane key="cs">
<span slot="tab">
<a-icon type="safety" />
<b>CloudStack Login</b>
Portal Login
</span>
<a-form-item>
<a-input
@ -78,11 +79,18 @@
</a-form-item>
</a-tab-pane>
<a-tab-pane key="tab2" disabled>
<a-tab-pane key="saml" :disabled="idps.length === 0">
<span slot="tab">
<a-icon type="audit" />
<b>SAML</b>
Single-Sign-On
</span>
<a-form-item>
<a-select v-decorator="['idp', { initialValue: selectedIdp } ]">
<a-select-option v-for="(idp, idx) in idps" :key="idx" :value="idp.id">
{{ idp.orgName }}
</a-select-option>
</a-select>
</a-form-item>
</a-tab-pane>
</a-tabs>
@ -100,14 +108,18 @@
</template>
<script>
import { api } from '@/api'
import { mapActions } from 'vuex'
import config from '@/config/settings'
export default {
components: {
},
data () {
return {
customActiveKey: 'tab1',
idps: [],
selectedIdp: '',
customActiveKey: 'cs',
loginBtn: false,
loginType: 0,
form: this.$form.createForm(this),
@ -120,8 +132,19 @@ export default {
},
created () {
},
mounted () {
this.fetchData()
},
methods: {
...mapActions(['Login', 'Logout']),
fetchData () {
api('listIdps').then(response => {
if (response) {
this.idps = response.listidpsresponse.idp || []
this.selectedIdp = this.idps[0].id || ''
}
})
},
// handler
handleUsernameOrEmail (rule, value, callback) {
const { state } = this
@ -148,24 +171,33 @@ export default {
state.loginBtn = true
const validateFieldsKey = customActiveKey === 'tab1' ? ['username', 'password', 'domain'] : ['mobile', 'captcha']
const validateFieldsKey = customActiveKey === 'cs' ? ['username', 'password', 'domain'] : ['idp']
validateFields(validateFieldsKey, { force: true }, (err, values) => {
if (!err) {
const loginParams = { ...values }
delete loginParams.username
loginParams[!state.loginType ? 'email' : 'username'] = values.username
loginParams.password = values.password
loginParams.domain = values.domain
if (!loginParams.domain) {
loginParams.domain = '/'
if (customActiveKey === 'cs') {
const loginParams = { ...values }
delete loginParams.username
loginParams[!state.loginType ? 'email' : 'username'] = values.username
loginParams.password = values.password
loginParams.domain = values.domain
if (!loginParams.domain) {
loginParams.domain = '/'
}
Login(loginParams)
.then((res) => this.loginSuccess(res))
.catch(err => this.requestFailed(err))
.finally(() => {
state.loginBtn = false
})
} else if (customActiveKey === 'saml') {
state.loginBtn = false
var samlUrl = config.apiBase + '?command=samlSso'
if (values.idp) {
samlUrl += ('&idpid=' + values.idp)
}
window.location.href = samlUrl
}
Login(loginParams)
.then((res) => this.loginSuccess(res))
.catch(err => this.requestFailed(err))
.finally(() => {
state.loginBtn = false
})
} else {
setTimeout(() => {
state.loginBtn = false
@ -174,7 +206,6 @@ export default {
})
},
loginSuccess (res) {
this.$message.loading('Login Successful. Discovering Features...', 5)
this.$router.push({ path: '/dashboard' }).catch(() => {})
},
requestFailed (err) {
@ -204,6 +235,7 @@ export default {
}
button.login-button {
margin-top: 8px;
padding: 0 15px;
font-size: 16px;
height: 40px;