api2/access.rs: add ticket api

This commit is contained in:
Dietmar Maurer 2019-01-30 15:14:20 +01:00
parent 1bf446a33e
commit 34f956bc25
8 changed files with 262 additions and 11 deletions

View File

@ -10,6 +10,7 @@ pub mod admin;
pub mod node; pub mod node;
mod version; mod version;
mod subscription; mod subscription;
mod access;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use crate::tools::common_regex; use crate::tools::common_regex;
@ -79,13 +80,15 @@ pub fn router() -> Router {
let route = Router::new() let route = Router::new()
.get(ApiMethod::new( .get(ApiMethod::new(
|_,_,_| Ok(json!([ |_,_,_| Ok(json!([
{"subdir": "config"}, {"subdir": "access"},
{"subdir": "admin"}, {"subdir": "admin"},
{"subdir": "config"},
{"subdir": "nodes"}, {"subdir": "nodes"},
{"subdir": "subscription"}, {"subdir": "subscription"},
{"subdir": "version"}, {"subdir": "version"},
])), ])),
ObjectSchema::new("Directory index."))) ObjectSchema::new("Directory index.")))
.subdir("access", access::router())
.subdir("admin", admin::router()) .subdir("admin", admin::router())
.subdir("config", config::router()) .subdir("config", config::router())
.subdir("nodes", nodes) .subdir("nodes", nodes)

86
src/api2/access.rs Normal file
View File

@ -0,0 +1,86 @@
use failure::*;
use crate::tools;
use crate::api::schema::*;
use crate::api::router::*;
use crate::tools::ticket::*;
use crate::auth_helpers::*;
use serde_json::{json, Value};
fn authenticate_user(username: &str, password: &str) -> Result<(), Error> {
if username == "root@pam" && password == "test" {
return Ok(());
}
bail!("inavlid credentials");
}
fn create_ticket(
param: Value,
_info: &ApiMethod,
rpcenv: &mut RpcEnvironment,
) -> Result<Value, Error> {
let username = tools::required_string_param(&param, "username")?;
let password = tools::required_string_param(&param, "password")?;
match authenticate_user(username, password) {
Ok(_) => {
let ticket = assemble_rsa_ticket( private_auth_key(), "PBS", None, None)?;
let token = assemble_csrf_prevention_token(csrf_secret(), username);
log::info!("successful auth for user '{}'", username);
return Ok(json!({
"username": username,
"ticket": ticket,
"CSRFPreventionToken": token,
}));
}
Err(err) => {
let client_ip = "unknown"; // $rpcenv->get_client_ip() || '';
log::error!("authentication failure; rhost={} user={} msg={}", client_ip, username, err.to_string());
bail!("authentication failure");
}
}
}
pub fn router() -> Router {
let route = Router::new()
.get(ApiMethod::new(
|_,_,_| Ok(json!([
{"subdir": "ticket"}
])),
ObjectSchema::new("Directory index.")))
.subdir(
"ticket",
Router::new()
.post(
ApiMethod::new(
create_ticket,
ObjectSchema::new("Create or verify authentication ticket.")
.required(
"username",
StringSchema::new("User name.")
.max_length(64)
)
.required(
"password",
StringSchema::new("The secret password. This can also be a valid ticket.")
)
).returns(
ObjectSchema::new("Returns authentication ticket with additional infos.")
.required("username", StringSchema::new("User name."))
.required("ticket", StringSchema::new("Auth ticket."))
.required("CSRFPreventionToken", StringSchema::new("Cross Site Request Forgery Prevention Token."))
).protected(true)
)
);
route
}

View File

@ -247,8 +247,8 @@ fn handle_async_api_request(
fn get_index() -> BoxFut { fn get_index() -> BoxFut {
let nodename = tools::nodename(); let nodename = tools::nodename();
let username = "fakelogin"; // todo: implement real auth let username = ""; // fixme: implement real auth
let token = "abc"; let token = "";
let setup = json!({ let setup = json!({
"Setup": { "auth_cookie_name": "PBSAuthCookie" }, "Setup": { "auth_cookie_name": "PBSAuthCookie" },

View File

@ -20,15 +20,30 @@ Ext.define('PBS.Application', {
logout: function() { logout: function() {
var me = this; var me = this;
//Proxmox.Utils.authClear(); Proxmox.Utils.authClear();
//me.changeView('loginview', true); me.changeView('loginview', true);
}, },
changeView: function(view, skipCheck) { changeView: function(view, skipCheck) {
var me = this; var me = this;
//? PBS.view = view;
me.view = view;
if (me.currentView != undefined) {
me.currentView.destroy();
}
me.currentView = Ext.create({
xtype: view,
});
if (skipCheck !== true) {
// fixme:
// Proxmox.Utils.checked_command(function() {}); // display subscription status
}
}, },
view: 'loginview',
launch: function() { launch: function() {
var me = this; var me = this;
Ext.on('resize', me.realignWindows); Ext.on('resize', me.realignWindows);
@ -36,11 +51,13 @@ Ext.define('PBS.Application', {
var provider = new Ext.state.LocalStorageProvider({ prefix: 'ext-pbs-' }); var provider = new Ext.state.LocalStorageProvider({ prefix: 'ext-pbs-' });
Ext.state.Manager.setProvider(provider); Ext.state.Manager.setProvider(provider);
// fixme: show login window if not loggedin // show login window if not loggedin
var loggedin = Proxmox.Utils.authOK();
me.currentView = Ext.create({ if (!loggedin) {
xtype: 'mainview' me.changeView('loginview', true);
}); } else {
me.changeView('mainview', true);
}
} }
}); });

128
www/LoginView.js Normal file
View File

@ -0,0 +1,128 @@
Ext.define('PBS.LoginView', {
extend: 'Ext.container.Container',
xtype: 'loginview',
controller: {
xclass: 'Ext.app.ViewController',
submitForm: function() {
var me = this;
var view = me.getView();
var loginForm = me.lookupReference('loginForm');
if (loginForm.isValid()) {
if (loginForm.isVisible()) {
loginForm.mask(gettext('Please wait...'), 'x-mask-loading');
}
loginForm.submit({
success: function(form, action) {
// save login data and create cookie
PBS.Utils.updateLoginData(action.result.data);
PBS.app.changeView('mainview');
},
failure: function(form, action) {
loginForm.unmask();
Ext.MessageBox.alert(
gettext('Error'),
gettext('Login failed. Please try again')
);
}
});
}
},
control: {
'button[reference=loginButton]': {
click: 'submitForm'
}
}
},
plugins: 'viewport',
layout: {
type: 'border'
},
items: [
{
region: 'north',
xtype: 'container',
layout: {
type: 'hbox',
align: 'middle'
},
margin: '2 5 2 5',
height: 38,
items: [
{
xtype: 'proxmoxlogo'
},
{
xtype: 'versioninfo',
makeApiCall: false
}
]
},
{
region: 'center'
},
{
xtype: 'window',
closable: false,
resizable: false,
reference: 'loginwindow',
autoShow: true,
modal: true,
defaultFocus: 'usernameField',
layout: {
type: 'auto'
},
title: gettext('Proxmox Backup Server Login'),
items: [
{
xtype: 'form',
layout: {
type: 'form'
},
defaultButton: 'loginButton',
url: '/api2/extjs/access/ticket',
reference: 'loginForm',
fieldDefaults: {
labelAlign: 'right',
allowBlank: false
},
items: [
{
xtype: 'textfield',
fieldLabel: gettext('User name'),
name: 'username',
itemId: 'usernameField',
reference: 'usernameField'
},
{
xtype: 'textfield',
inputType: 'password',
fieldLabel: gettext('Password'),
name: 'password',
reference: 'passwordField'
}
],
buttons: [
{
text: gettext('Login'),
reference: 'loginButton',
formBind: true
}
]
}
]
}
]
});

View File

@ -82,10 +82,20 @@ Ext.define('PBS.MainView', {
}, },
logout: function() {
PBS.app.logout();
},
navigate: function(treelist, item) { navigate: function(treelist, item) {
this.redirectTo(item.get('path')); this.redirectTo(item.get('path'));
}, },
control: {
'button[reference=logoutButton]': {
click: 'logout'
}
},
init: function(view) { init: function(view) {
var me = this; var me = this;
console.log("init"); console.log("init");

View File

@ -1,6 +1,7 @@
JSSRC= \ JSSRC= \
Utils.js \ Utils.js \
Logo.js \ Logo.js \
LoginView.js \
VersionInfo.js \ VersionInfo.js \
SystemConfiguration.js \ SystemConfiguration.js \
Subscription.js \ Subscription.js \

View File

@ -6,6 +6,12 @@ console.log("Starting Backup Server GUI");
Ext.define('PBS.Utils', { Ext.define('PBS.Utils', {
singleton: true, singleton: true,
updateLoginData: function(data) {
Proxmox.CSRFPreventionToken = data.CSRFPreventionToken;
Proxmox.UserName = data.username;
Ext.util.Cookies.set('PBSAuthCookie', data.ticket, null, '/', null, true );
},
constructor: function() { constructor: function() {
var me = this; var me = this;