Skip to content

Commit

Permalink
jupyter_server: support Cylc UIS Jupyter Server extension
Browse files Browse the repository at this point in the history
* cylc/cylc-uiserver#230
* The UIS is now a Jupyter Server extension.
* The endpoint is now `cylc/#/` rather than `#/`
* Authentication via token/cookie is now supported.
* Sets XSRF header to support new hubless single-user mode.
  • Loading branch information
oliver-sanders committed Aug 17, 2021
1 parent a973097 commit ed64bb4
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ release.

[#502](https://github.com/cylc/cylc-ui/pull/502) - Hierarchical GScan.

[#711](https://github.com/cylc/cylc-ui/pull/711) -
Support Jupyter Server conversion. (the CylcUIServer has been converted
to a Jupyter Server extension).

### Fixes

-------------------------------------------------------------------------------
Expand Down
5 changes: 3 additions & 2 deletions src/graphql/graphiql.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// Code related to GraphiQL

import { parse } from 'graphql'
import { createGraphQLUrls } from '@/graphql/index'
import { createGraphQLUrls, getCylcHeaders } from '@/graphql/index'

// TODO: https://github.com/apollographql/GraphiQL-Subscriptions-Fetcher/issues/16
// the functions hasSubscriptionOperation and graphQLFetcher are both from
Expand Down Expand Up @@ -113,7 +113,8 @@ function fallbackGraphQLFetcher (graphQLParams) {
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
'Content-Type': 'application/json',
...getCylcHeaders()
},
body: JSON.stringify(graphQLParams),
credentials: 'include'
Expand Down
28 changes: 27 additions & 1 deletion src/graphql/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from '@apollo/client'
import { getMainDefinition } from '@apollo/client/utilities'
import { WebSocketLink } from '@apollo/client/link/ws'
import { setContext } from '@apollo/client/link/context'
import { SubscriptionClient } from 'subscriptions-transport-ws'
import store from '@/store/index'
import { createUrl } from '@/utils/urls'
Expand All @@ -43,6 +44,21 @@ export function createGraphQLUrls () {
}
}

/**
* Get request headers for use with UI Server requests.
*
* - Adds X-XSRFToken header for hubless token based auth.
*/
export function getCylcHeaders () {
const xsrfToken = document.cookie.match('\\b_xsrf=([^;]*)\\b')
const cylcHeaders = {}
if (Array.isArray(xsrfToken) && xsrfToken.length > 0) {
// pick the last match
cylcHeaders['X-XSRFToken'] = xsrfToken.splice(-1)
}
return cylcHeaders
}

/**
* Create a subscription client.
*
Expand Down Expand Up @@ -123,8 +139,18 @@ export function createApolloClient (httpUrl, subscriptionClient) {
httpLink
)

const wsAuthLink = setContext((_, { headers }) => {
// add an X-XSRFToken header for hubless token based auth
return {
headers: {
...headers,
...getCylcHeaders()
}
}
})

return new ApolloClient({
link: link,
link: wsAuthLink.concat(link),
cache: new InMemoryCache(),
defaultOptions: {
query: {
Expand Down
2 changes: 1 addition & 1 deletion src/mixins/graphql.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default {
* @return {string} - the Workflow ID used in this view
*/
workflowId () {
return `${this.user.username}|${this.workflowName}`
return `${this.user.owner}|${this.workflowName}`
},
/**
* GraphQL query variables.
Expand Down
10 changes: 8 additions & 2 deletions src/model/User.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@
* @property {string} server - server URL
*/
export default class User {
constructor (username, groups, created, admin, server) {
constructor (username, groups, created, admin, server, owner) {
// the authenticated user
// (full info only available when authenticated via the hub)
this.username = username
this.groups = groups
this.created = created
this.admin = admin
this.server = server
this.server = server || '?' // server can be unset
// the UIS owner
// (i.e. the user who's workflows we are looking at)
// (this might not be the authenticated user for multi-user setups)
this.owner = owner
}
}
9 changes: 8 additions & 1 deletion src/services/user.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ class UserService {
*/
getUserProfile () {
return axios.get(createUrl('userprofile')).then((response) => {
return new User(response.data.name, response.data.groups, response.data.created, response.data.admin, response.data.server)
return new User(
response.data.name,
response.data.groups,
response.data.created,
response.data.admin,
response.data.server,
response.data.owner
)
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/mixins/graphql.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('GraphQL mixin', () => {
})
const variables = component.vm.variables
const expected = {
workflowId: `${user.username}|${workflowName}`
workflowId: `${user.owner}|${workflowName}`
}
expect(variables).to.deep.equal(expected)
})
Expand Down

0 comments on commit ed64bb4

Please sign in to comment.