view release on metacpan or search on metacpan
0.17.0 2019-05-31 16:02:43 +0200 Tobias Oetiker <tobi@oetiker.ch>
- add the triggerFormReset key to form elements, when a widget has this key
set the form configuration will be re-queried from the server, and the
current content of the form will be available in $self->args. In this
way it is possible to change settings of the form widgets based on the current
form content, as well as altering the content of selectboxes.
0.16.0 2019-04-23 17:04:50 +0200 Tobias Oetiker <tobi@oetiker.ch>
- if the getUserConfig returns a sessionCookie property, start using it
to authenticate and thus skipping the login step. this can be used to
implement single sign on ... see bdass.
0.15.1 2019-04-03 15:44:12 +0200 Tobias Oetiker <tobi@oetiker.ch>
- added missing NumberFormatter to archive
0.15.0 2019-04-03 15:13:53 +0200 Tobias Oetiker <tobi@oetiker.ch>
- added support for numberformating ... 1000 Byte => 1 kByte
lib/CallBackery/Controller/RpcService.pm view on Meta::CPAN
my %allow = (
getBaseConfig => 1,
login => 1,
logout => 1,
ping => 1,
getUserConfig => 2,
getPluginConfig => 3,
validatePluginData => 3,
processPluginData => 3,
getPluginData => 3,
getSessionCookie => 2
);
has config => sub ($self) {
$self->app->config;
}, weak => 1;
has user => sub ($self) {
my $obj = $self->app->userObject->new(app=>$self->app,controller=>$self,log=>$self->log);
return $obj;
};
lib/CallBackery/Controller/RpcService.pm view on Meta::CPAN
=head2 ping()
check if the server is happy with our authentication state
=cut
sub ping {
return 'pong';
}
=head2 getSessionCookie()
Return a timeestamped session cookie. For use in the X-Session-Cookie header or as a xsc field
in form submissions. Note that session cookies for form submissions are only valid for 2 seconds.
So you have to get a fresh one from the server before submitting your form.
=cut
sub getSessionCookie {
shift->user->makeSessionCookie();
}
=head2 getConfig()
get some gloabal configuration information into the interface
=cut
sub getBaseConfig {
my $self = shift;
lib/CallBackery/Controller/RpcService.pm view on Meta::CPAN
=cut
async sub login { ## no critic (RequireArgUnpacking)
my $self = shift;
my $login = shift;
my $password = shift;
my $cfg = $self->config->cfgHash->{BACKEND};
if (my $ok =
await $self->config->promisify($self->user->login($login,$password))){
return {
sessionCookie => $self->user->makeSessionCookie()
}
} else {
return;
}
}
=head2 logout
Kill the session.
lib/CallBackery/User.pm view on Meta::CPAN
by whichever layer detects expiry (e.g. a subclass' C<cookieConf> that enforces a
max cookie age); read by the RPC dispatcher to choose the session-expired signal
(code 7) over the login-required signal (code 6). Per-request.
=cut
has sessionExpired => 0;
=head2 $self->sessionConf
Extracts the session config from the cookie from the X-Session-Cookie header or the xsc parameter.
If the xsc parameter is set, its timestamp must be no older than 2 seconds.
=cut
has headerSessionCookie => sub {
my $self = shift;
my $c = $self->controller;
return $c->req->headers->header('X-Session-Cookie');
};
has paramSessionCookie => sub {
my $self = shift;
my $c = $self->controller;
return $c->param('xsc');
};
has firstSecret => sub {
shift->app->secrets()->[0];
};
sub isUserAuthenticated {
my $self = shift;
$self->userInfo->{cbuser_id} ? 1 : 0;
};
has cookieConf => sub {
my $self = shift;
my $headerCookie = $self->headerSessionCookie;
my $paramCookie = $self->paramSessionCookie;
my ($data,$check) = split /:/,($headerCookie || $paramCookie || ''),2;
return {} if not ($data and $check);
my $secret = $self->firstSecret;
my $checkTest = Mojo::Util::hmac_sha1_sum($data, $secret);
if (not secure_compare($check,$checkTest)){
$self->log->debug(qq{Bad signed cookie possible hacking attempt.});
return {};
}
lib/CallBackery/User.pm view on Meta::CPAN
my $conf = eval {
local $SIG{__DIE__};
decode_json(b64_decode($data))
};
if ($@){
$self->log->debug("Invalid cookie structure in '$data': $@");
return {};
}
if (ref $conf ne 'HASH'){
$self->log->debug("Cookie structure not a hash");
return {};
}
if (not $conf->{t}){
$self->log->debug("Cookie timestamp is invalid");
return {};
}
if ($paramCookie and gettimeofday() - $conf->{t} > 300.0){
$self->log->debug(qq{Cookie is expired});
die mkerror(38445,"cookie has expired");
}
return $conf;
};
=head2 $user->login($login,$password)
login the user object. If login return 1 you can then makeSessionCookie.
=cut
sub login {
my $self = shift;
my $login = shift;
my $password = shift;
my $cfg = $self->app->config->cfgHash;
my $remoteAddress = eval { $self->controller->tx->remote_address } // 'UNKNOWN_IP';
if ($cfg->{sesame_pass} and $cfg->{sesame_user}
lib/CallBackery/User.pm view on Meta::CPAN
# root has all the rights
if (($self->userId // '') eq '__ROOT'){
return 1;
}
my $db = $self->db;
my $rightId = $db->lookUp('cbright','key',$right);
my $userId = $self->userId;
return ($db->matchData('cbuserright',{cbuser=>$userId,cbright=>$rightId}) ? 1 : 0);
}
=head2 makeSessionCookie()
Returns a timestamped, signed session cookie containing the current userId.
=cut
sub makeSessionCookie {
my $self = shift;
my $timeout = shift;
my $now = gettimeofday;
my $conf = b64_encode(encode_json({
u => $self->userId,
t => $now,
}));
$conf =~ s/\s+//g;
my $secret = $self->firstSecret;
my $check = Mojo::Util::hmac_sha1_sum($conf, $secret);
lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/data/Config.js view on Meta::CPAN
if (base){
base[1].split(/;/).forEach(function(kv){
var list = kv.split('=');
ha[list[0]] = decodeURIComponent(list[1]);
});
}
window.location.hash = '';
return ha;
},
/* if there is a sessonCookie in the userConfig start using it. This allows for seemless login */
_applyUserConfig: function(newData,oldData) {
if (newData.userInfo && newData.userInfo.sessionCookie) {
callbackery.data.Server.getInstance().setSessionCookie(newData.userInfo.sessionCookie);
}
}
}
});
lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/data/Server.js view on Meta::CPAN
* API (callAsync/callAsyncSmart/callAsyncSmartBusy with a (ret, exc, id)
* handler) so existing call sites keep working unchanged.
*
* @use(qx.io.transport.Xhr)
*/
qx.Class.define("callbackery.data.Server", {
extend: qx.core.Object,
type: "singleton",
properties: {
sessionCookie: {
init: null,
nullable: true
},
url: {
init: 'QX-JSON-RPC/',
check: 'String'
},
timeout: {
init: 180000,
check: 'Integer'
lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/data/Server.js view on Meta::CPAN
__commsErrorHandled: false,
// Bounded silent retry for transient communication failures.
_COMMS_RETRY_MAX: 3,
_COMMS_RETRY_BACKOFF_MS: 1300,
// Idempotent methods that are safe to retry silently. Writes
// (processPluginData) and auth calls (login/logout) are excluded.
_retrySafe: {
ping: true, getBaseConfig: true, getUserConfig: true,
getSessionCookie: true, getPluginConfig: true,
getPluginData: true, validatePluginData: true
},
/**
* Lazily create the JSON-RPC client and wire the session-cookie header.
* @return {qx.io.jsonrpc.Client}
*/
_getClient: function() {
if (!this.__client) {
var self = this;
var transport = new qx.io.transport.Xhr(this.getUrl());
var client = new qx.io.jsonrpc.Client(transport);
client.addListener('outgoingRequest', function() {
// getTransportImpl() creates a fresh impl that the
// immediately-following Xhr.send() will pick up and reuse.
var impl = transport.getTransportImpl();
impl.setTimeout(self.getTimeout());
var cookie = self.getSessionCookie();
if (cookie) {
impl.setRequestHeader('X-Session-Cookie', cookie);
}
});
this.__client = client;
}
return this.__client;
},
/**
* Recursively replace non-finite numbers (NaN, Infinity) with null in
* plain data so the outgoing JSON is valid. qx.io.jsonrpc serialises
lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/data/Server.js view on Meta::CPAN
// only application (JSON-RPC protocol) errors carry the 6/7
// codes the login/session logic reacts to; transport errors
// fall through to the normal handler.
if (exc && exc.application) {
switch (exc.code) {
case 6: {
// permission denied: prompt for login, then retry once
let login = callbackery.ui.Login.getInstance();
login.addListenerOnce('login', (e) => {
let r = e.getData();
origThis.setSessionCookie(r.sessionCookie);
// retry the original call (without re-wrapping)
origThis._send(origHandler, origMethod, origArgs);
});
login.open();
return;
}
case 7: {
// Session expired: exactly one reload prompt owns the
// UX; every other concurrent expired call is swallowed.
if (origThis.__sessionExpiredHandled) { return; }
lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/Login.js view on Meta::CPAN
iframe.setAttribute('src',
'login?nocache='+Math.round(1000000*Math.random()).toString(16));
return iframe;
},
__getIframeDocument: function(){
var iframe = this.__iframe;
return iframe.contentWindow ? iframe.contentWindow.document : iframe.contentDocument;
},
__loginHandler : function(username, password, ret, exc) {
if (exc == null) {
if (qx.lang.Type.isObject(ret) && ret.sessionCookie) {
this.__rememberCredentials(username, password);
this.fireDataEvent('login', ret);
this.close();
}
else {
var element = this.getContentElement().getDomElement();
var tada = {duration: 1000, keyFrames : {
0 : {scale: 1, rotate: "0deg"},
10 : {scale: 0.9, rotate: "-3deg"},
20 : {scale: 0.9, rotate: "-3deg"},
lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Action.js view on Meta::CPAN
that.xtr(response.exception.message) + " (" + response.exception.code + ")"
);
}
that.getApplicationRoot().remove(iframe);
if (btCfg.closeAfterDownload) {
that.fireDataEvent('actionResponse', { action: 'cancel' });
}
});
iframe.setSource(url);
that.getApplicationRoot().add(iframe, { top: -1000, left: -1000 });
}, 'getSessionCookie');
break;
case 'cancel':
this.fireDataEvent('actionResponse', { action: 'cancel' });
break;
case 'wizzard':
var parent = that.getLayoutParent();
while (!parent.classname.match(/Page|Popup/)) {
parent = parent.getLayoutParent();
}
// This could in principal work for Page although.
lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Action.js view on Meta::CPAN
if (formData && fileList) {
var form = new FormData();
form.append('name', name);
form.append('key', key);
form.append('file', fileList[0]);
form.append('formData', qx.lang.Json.stringify(formData));
var that = this;
serverCall.callAsyncSmart(function (cookie) {
form.append('xsc', cookie);
that._uploadForm(form,btCfg.busyMessage);
}, 'getSessionCookie');
} else {
callbackery.ui.MsgBox.getInstance().error(
this.tr("Upload Exception"),
this.tr("Make sure to select a file and properly fill the form")
);
}
}, this);
return button;
},