Commit 749d76f5 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Switched to token based GUI authentification.

parent 3f475478
......@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
import Markdown from './Markdown'
import gitInfo from '../gitinfo'
import { kibanaBase, appBase, apiBase } from '../config'
import { kibanaBase, apiBase } from '../config'
class Development extends React.Component {
static propTypes = {
......
......@@ -60,7 +60,7 @@ class LoginLogoutComponent extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
api: PropTypes.object.isRequired,
userName: PropTypes.string,
user: PropTypes.object,
login: PropTypes.func.isRequired,
logout: PropTypes.func.isRequired
}
......@@ -124,13 +124,13 @@ class LoginLogoutComponent extends React.Component {
}
render() {
const { classes, userName } = this.props
const { classes, user } = this.props
const { loggingIn, failure } = this.state
if (userName) {
if (user) {
return (
<div className={classes.root}>
<Typography color="inherit" variant="body1">
Welcome, {userName}
Welcome, {user.first_name} {user.last_name}
</Typography>
<Button color="inherit" variant="outlined" onClick={this.handleLogout}>Logout</Button>
</div>
......
......@@ -99,23 +99,30 @@ class Upload {
}
class Api {
constructor(userName, password) {
userName = userName || 'sheldon.cooper@nomad-fairdi.tests.de'
password = password || 'password'
this.swaggerPromise = Swagger(`${apiBase}/swagger.json`, {
authorizations: {
'HTTP Basic': {
username: userName,
password: password
static async createSwaggerClient(userNameToken, password) {
let data
if (userNameToken) {
let auth = {
'X-Token': userNameToken
}
if (password) {
auth = {
'HTTP Basic': {
username: userNameToken,
password: password
}
}
}
})
this.auth_headers = {
Authorization: 'Basic ' + btoa(`${userName}:${password}`)
data = {authorizations: auth}
}
return Swagger(`${apiBase}/swagger.json`, data)
}
constructor(user) {
user = user || {}
this.swaggerPromise = Api.createSwaggerClient(user.token)
this.handleApiError = this.handleApiError.bind(this)
}
......@@ -214,7 +221,7 @@ class Api {
.then(response => response.body)
}
async authenticate(userName, password) {
static async authenticate(userName, password) {
const client = await this.swaggerPromise
return client.apis.auth.get_token()
.catch(error => {
......@@ -280,21 +287,31 @@ export class ApiProvider extends React.Component {
}
state = {
errors: [],
api: new Api(),
token: null,
userName: null,
user: null,
login: (userName, password, callback) => {
const api = new Api(userName, password)
api.authenticate().then(result => {
if (result) {
this.setState({api: api, userName: userName})
}
callback(result)
})
Api.createSwaggerClient(userName, password)
.catch(this.state.api.handleApiError)
.then(client => {
client.apis.auth.get_user()
.catch(error => {
if (error.response.status !== 401) {
this.handleApiError(error)
}
})
.then(response => {
if (response) {
const user = response.body
this.setState({api: new Api(user), user: user})
callback(true)
} else {
callback(false)
}
})
})
},
logout: () => {
this.setState({api: new Api(), userName: null})
this.setState({api: new Api(), user: null})
}
}
......@@ -314,9 +331,9 @@ export function withApi(loginRequired) {
return (
<ApiContext.Consumer>
{apiContext => (
(apiContext.userName || !loginRequired)
(apiContext.user || !loginRequired)
? <Component
{...props} api={apiContext.api} userName={apiContext.userName}
{...props} api={apiContext.api} user={apiContext.user}
login={apiContext.login} logout={apiContext.logout} />
: <Typography color="error">Please login to use this functionality</Typography>
)}
......
......@@ -35,8 +35,8 @@ authenticated user information for authorization or otherwise.
.. autofunction:: login_really_required
"""
from flask import g, request, make_response
from flask_restplus import abort, Resource
from flask import g, request
from flask_restplus import abort, Resource, fields
from flask_httpauth import HTTPBasicAuth
from nomad import config, processing, files, utils, coe_repo
......@@ -126,10 +126,21 @@ ns = api.namespace(
description='Authentication related endpoints.')
@ns.route('/token')
user_model = api.model('User', {
'first_name': fields.String(description='The user\'s first name'),
'last_name': fields.String(description='The user\'s last name'),
'email': fields.String(description='Guess what, the user\'s email'),
'affiliation': fields.String(description='The user\'s affiliation'),
'token': fields.String(
description='The access token that authenticates the user with the API. '
'User the HTTP header "X-Token" to provide it in API requests.')
})
@ns.route('/user')
class TokenResource(Resource):
@api.doc('get_token')
@api.response(200, 'Token send', headers={'Content-Type': 'text/plain; charset=utf-8'})
@api.doc('get_user')
@api.marshal_with(user_model, skip_none=True, code=200, description='User data send')
@login_really_required
def get(self):
"""
......@@ -140,14 +151,11 @@ class TokenResource(Resource):
a more secure method of authentication.
"""
try:
response = make_response(g.user.get_auth_token().decode('utf-8'))
response.headers['Content-Type'] = 'text/plain; charset=utf-8'
return response
return g.user
except LoginException:
abort(
401,
message='You are not propertly logged in at the NOMAD coe repository, '
'there is no token for you.')
message='User not logged in, provide credentials via Basic HTTP authentication.')
def create_authorization_predicate(upload_id, calc_id=None):
......
......@@ -43,8 +43,9 @@ class User(Base): # type: ignore
user_id = Column(Integer, primary_key=True)
email = Column(String)
firstname = Column(String)
lastname = Column(String)
first_name = Column(String, name='firstname')
last_name = Column(String, name='lastname')
affiliation = Column(String)
password = Column(String)
def __repr__(self):
......@@ -73,6 +74,10 @@ class User(Base): # type: ignore
return session.token.encode('utf-8')
@property
def token(self):
return self.get_auth_token().decode('utf-8')
@property
def is_admin(self) -> bool:
return self.email == 'admin'
......
......@@ -178,7 +178,7 @@ class NomadCOEMigration:
admin = target_db.query(User).filter_by(email='admin').first()
if admin is None:
admin = User(
user_id=0, email='admin', firstname='admin', lastname='admin',
user_id=0, email='admin', first_name='admin', last_name='admin',
password=bcrypt.encrypt(config.services.admin_password, ident='2y'))
target_db.add(admin)
target_db.commit()
......
......@@ -146,10 +146,18 @@ class TestAuth:
})
assert rv.status_code == 401
def test_get_token(self, client, test_user_auth, test_user: User, no_warn):
rv = client.get('/auth/token', headers=test_user_auth)
def test_get_user(self, client, test_user_auth, test_user: User, no_warn):
rv = client.get('/auth/user', headers=test_user_auth)
assert rv.status_code == 200
user = json.loads(rv.data)
for key in ['first_name', 'last_name', 'email', 'token']:
assert key in user
rv = client.get('/uploads/', headers={
'X-Token': user['token']
})
assert rv.status_code == 200
assert rv.data.decode('utf-8') == test_user.get_auth_token().decode('utf-8')
class TestUploads:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment