CallBackery

 view release on metacpan or  search on metacpan

CHANGES  view on Meta::CPAN

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;
        },



( run in 1.567 second using v1.01-cache-2.11-cpan-84e82930d8c )