mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-22 07:53:39 +01:00
WIP Mail Rest API: UI for application passwords/tokens for admin
This commit is contained in:
parent
e210d4b3c6
commit
07300704bc
@ -72,6 +72,7 @@ class admin_hooks
|
||||
if (! $GLOBALS['egw']->acl->check('account_access',16,'admin'))
|
||||
{
|
||||
$file['Bulk password reset'] = Egw::link('/index.php','menuaction=admin.admin_passwordreset.index&ajax=true');
|
||||
$file['Application passwords'] = Egw::link('/index.php', 'menuaction=admin.EGroupware\\Admin\\Token.index&ajax=true');
|
||||
}
|
||||
|
||||
if (! $GLOBALS['egw']->acl->check('group_access',1,'admin'))
|
||||
|
@ -19,6 +19,7 @@ import {egw} from "../../api/js/jsapi/egw_global.js";
|
||||
import {egwAction, egwActionObject} from '../../api/js/egw_action/egw_action.js';
|
||||
import {LitElement} from "@lion/core";
|
||||
import {et2_nextmatch} from "../../api/js/etemplate/et2_extension_nextmatch";
|
||||
import {et2_DOMWidget} from "../../api/js/etemplate/et2_core_DOMWidget";
|
||||
|
||||
/**
|
||||
* UI for Admin
|
||||
@ -1643,6 +1644,24 @@ class AdminApp extends EgwApp
|
||||
}
|
||||
}, this).sendRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clickhandler to copy given text or widget content to clipboard
|
||||
* @param _widget
|
||||
* @param _text default widget content
|
||||
*/
|
||||
copyClipboard(_widget : et2_DOMWidget, _text? : string, _event? : Event)
|
||||
{
|
||||
let value = _text || (typeof _widget.get_value === 'function' ? _widget.get_value() : _widget.options.value);
|
||||
let node = _widget.getDOMNode() !== _widget ? _widget.getDOMNode() : _widget;
|
||||
this.egw.copyTextToClipboard(value, node, _event).then((success) =>
|
||||
{
|
||||
if(success !== false)
|
||||
{
|
||||
this.egw.message(this.egw.lang("Copied '%1' to clipboard", value), 'success');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
app.classes.admin = AdminApp;
|
268
admin/src/Token.php
Normal file
268
admin/src/Token.php
Normal file
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
/**
|
||||
* EGroupware - Admin - Application passwords / tokens
|
||||
*
|
||||
* @link https://www.egroupware.org
|
||||
* @author Ralf Becker <rb-AT-egroupware.org>
|
||||
* @package admin
|
||||
* @copyright (c) 2023 by Ralf Becker <rb-AT-egroupware.org>
|
||||
* @license https://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
*/
|
||||
|
||||
namespace EGroupware\Admin;
|
||||
|
||||
use EGroupware\Api;
|
||||
|
||||
class Token
|
||||
{
|
||||
const APP = 'admin';
|
||||
|
||||
/**
|
||||
* Methods callable via menuaction GET parameter
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $public_functions = [
|
||||
'index' => true,
|
||||
'edit' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Instance of our business object
|
||||
*
|
||||
* @var Api\Auth\Token
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->token = new Api\Auth\Token();
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a host
|
||||
*
|
||||
* @param array $content =null
|
||||
*/
|
||||
public function edit(array $content=null)
|
||||
{
|
||||
if (!is_array($content))
|
||||
{
|
||||
if (!empty($_GET['token_id']))
|
||||
{
|
||||
if (!($content = $this->token->read(['token_id' => $_GET['token_id']])))
|
||||
{
|
||||
Api\Framework::window_close(lang('Token not found!'));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$content = $this->token->init();
|
||||
if (empty($GLOBALS['egw_info']['user']['apps']['admin']))
|
||||
{
|
||||
$content['account_id'] = $GLOBALS['egw_info']['user']['account_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (!empty($content['button']))
|
||||
{
|
||||
try {
|
||||
$button = key($content['button']);
|
||||
unset($content['button']);
|
||||
switch($button)
|
||||
{
|
||||
case 'save':
|
||||
case 'apply':
|
||||
$content['token_limits'] = Api\Auth\Token::apps2limits($content['token_apps']);
|
||||
if (empty($content['token_id']))
|
||||
{
|
||||
$content = Api\Auth\Token::create($content['account_id'] ?: 0, $content['token_valid_until'], $content['token_remark'],
|
||||
$content['token_limits']);
|
||||
Api\Framework::refresh_opener(lang('Token created.'),
|
||||
self::APP, $this->token->data['token_id'],'add');
|
||||
$button = 'apply'; // must not close window to show token
|
||||
}
|
||||
elseif (!$this->token->save($content))
|
||||
{
|
||||
Api\Framework::refresh_opener(lang('Token saved.'),
|
||||
self::APP, $this->token->data['token_id'],'edit');
|
||||
$content = array_merge($content, $this->token->data);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \Exception(lang('Error storing token!'));
|
||||
}
|
||||
if ($button === 'save')
|
||||
{
|
||||
Api\Framework::window_close(); // does NOT return
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
if (!$this->token->revoke($content['token_id']))
|
||||
{
|
||||
Api\Framework::message(lang('Error revoking token!'));
|
||||
}
|
||||
else
|
||||
{
|
||||
Api\Framework::refresh_opener(lang('Token revoked.'),
|
||||
Bo::APP, $content['token_id'], 'update');
|
||||
|
||||
Api\Framework::window_close(); // does NOT return
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
Api\Framework::message($e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
$content['token_apps'] = Api\Auth\Token::limits2apps($content['token_limits']);
|
||||
if (empty($content['account_id'])) $content['account_id'] = '';
|
||||
$readonlys = [
|
||||
'button[delete]' => !$content['token_id'],
|
||||
'account_id' => empty($GLOBALS['egw_info']['user']['apps']['admin']),
|
||||
];
|
||||
$tmpl = new Api\Etemplate(self::APP.'.token.edit');
|
||||
$tmpl->exec(self::APP.'.'.self::class.'.edit', $content, [], $readonlys, $content, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch rows to display
|
||||
*
|
||||
* @param array $query
|
||||
* @param array& $rows =null
|
||||
* @param array& $readonlys =null
|
||||
*/
|
||||
public function get_rows($query, array &$rows=null, array &$readonlys=null)
|
||||
{
|
||||
$total = $this->token->get_rows($query, $rows, $readonlys);
|
||||
foreach($rows as &$row)
|
||||
{
|
||||
$row['token_apps'] = Api\Auth\Token::limits2apps($row['token_limits']);
|
||||
if ($row['token_revoked'])
|
||||
{
|
||||
$row['class'] = 'revoked';
|
||||
}
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Index
|
||||
*
|
||||
* @param array $content =null
|
||||
*/
|
||||
public function index(array $content=null)
|
||||
{
|
||||
if (!is_array($content) || empty($content['nm']))
|
||||
{
|
||||
$content = [
|
||||
'nm' => [
|
||||
'get_rows' => self::APP.'.'.__CLASS__.'.get_rows',
|
||||
'no_filter' => true, // disable the diverse filters we not (yet) use
|
||||
'no_filter2' => true,
|
||||
'no_cat' => true,
|
||||
'order' => 'token_id',// IO name of the column to sort after (optional for the sortheaders)
|
||||
'sort' => 'DESC',// IO direction of the sort: 'ASC' or 'DESC'
|
||||
'row_id' => 'token_id',
|
||||
'actions' => $this->get_actions(),
|
||||
'placeholder_actions' => array('add'),
|
||||
'add_link' => Api\Egw::link('/index.php', 'menuaction='.self::APP.'.'.self::class.'.edit'),
|
||||
]
|
||||
];
|
||||
}
|
||||
elseif(!empty($content['nm']['action']))
|
||||
{
|
||||
try {
|
||||
Api\Framework::message($this->action($content['nm']['action'],
|
||||
$content['nm']['selected'], $content['nm']['select_all']));
|
||||
}
|
||||
catch (\Exception $ex) {
|
||||
Api\Framework::message($ex->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
$tmpl = new Api\Etemplate(self::APP.'.tokens');
|
||||
$tmpl->exec(self::APP.'.'.self::class.'.index', $content, [
|
||||
'account_id' => ['0' => lang('All users')]
|
||||
], [], ['nm' => $content['nm']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions for cup list
|
||||
*
|
||||
* @param array $cont values for keys license_(nation|year|cat)
|
||||
* @return array
|
||||
*/
|
||||
protected function get_actions()
|
||||
{
|
||||
return [
|
||||
'edit' => [
|
||||
'caption' => 'Edit',
|
||||
'default' => true,
|
||||
'allowOnMultiple' => false,
|
||||
'url' => 'menuaction='.self::APP.'.'.self::class.'.edit&token_id=$id',
|
||||
'popup' => '640x480',
|
||||
'group' => $group=0,
|
||||
],
|
||||
'add' => [
|
||||
'caption' => 'Create',
|
||||
'url' => 'menuaction='.self::APP.'.'.self::class.'.edit',
|
||||
'popup' => '640x400',
|
||||
'group' => $group,
|
||||
],
|
||||
'activate' => [
|
||||
'caption' => 'Activate',
|
||||
'confirm' => 'Active this token again',
|
||||
'enableClass' => 'revoked',
|
||||
'group' => $group=5,
|
||||
],
|
||||
'revoke' => [
|
||||
'caption' => 'Revoke',
|
||||
'confirm' => 'Revoke this token',
|
||||
'icon' => 'delete',
|
||||
'disableClass' => 'revoked',
|
||||
'group' => $group,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute action on list
|
||||
*
|
||||
* @param string $action
|
||||
* @param array|int $selected
|
||||
* @param boolean $select_all
|
||||
* @returns string with success message
|
||||
* @throws Api\Exception\AssertionFailed
|
||||
*/
|
||||
protected function action($action, $selected, $select_all)
|
||||
{
|
||||
$success = 0;
|
||||
try {
|
||||
switch($action)
|
||||
{
|
||||
case 'revoke':
|
||||
case 'activate':
|
||||
$revoke = $action === 'revoke';
|
||||
foreach($selected as $token_id)
|
||||
{
|
||||
Api\Auth\Token::revoke($token_id, $revoke);
|
||||
++$success;
|
||||
}
|
||||
return lang('%1 token %2.', $success, $revoke ? lang('revoked') : lang('activated again'));
|
||||
|
||||
default:
|
||||
throw new Api\Exception\AssertionFailed('To be implemented ;)');
|
||||
}
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
if ($success) {
|
||||
$e = new \Exception($e->getMessage().', '.lang('%1 successful', $success), $e);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
@ -187,4 +187,15 @@ Admin command
|
||||
border-left: 0;
|
||||
border-bottom: 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/**
|
||||
* Application passwords / tokens
|
||||
*/
|
||||
/**
|
||||
* EPL application Firewall
|
||||
*/
|
||||
tr.revoked > td * {
|
||||
color: grey !important;
|
||||
font-style: italic;
|
||||
}
|
60
admin/templates/default/token.edit.xet
Normal file
60
admin/templates/default/token.edit.xet
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE overlay PUBLIC "-//EGroupware GmbH//eTemplate 2.0//EN" "https://www.egroupware.org/etemplate2.0.dtd">
|
||||
<overlay>
|
||||
<template id="admin.token.edit" template="" lang="" group="0" version="19.1">
|
||||
<grid width="100%">
|
||||
<columns>
|
||||
<column width="100"/>
|
||||
<column/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row disabled="!@token">
|
||||
<et2-description value="Token"></et2-description>
|
||||
<et2-textbox id="token" readonly="true" onclick="app.admin.copyClipboard(this)"></et2-textbox>
|
||||
</row>
|
||||
<row>
|
||||
<et2-description for="account_id" value="User"></et2-description>
|
||||
<et2-select-account id="account_id" accountType="user" emptyLabel="All users"></et2-select-account>
|
||||
</row>
|
||||
<row valign="top">
|
||||
<et2-description for="token_limits" value="Applications"></et2-description>
|
||||
<et2-vbox>
|
||||
<et2-select-app id="token_apps" multiple="true" placeholder="All applications of the user"></et2-select-app>
|
||||
<et2-description value="Select the applications you want the token to be limited to, or leave the default of all applications."></et2-description>
|
||||
</et2-vbox>
|
||||
</row>
|
||||
<row>
|
||||
<et2-description for="token_valid_until" value="Expiration"></et2-description>
|
||||
<et2-date id="token_valid_until" dataFormat="object"></et2-date>
|
||||
</row>
|
||||
<row>
|
||||
<et2-description for="token_remark" value="Remark"></et2-description>
|
||||
<et2-textarea id="token_remark" rows="5"></et2-textarea>
|
||||
</row>
|
||||
<row disabled="!@token_id">
|
||||
<et2-description value="Creator"></et2-description>
|
||||
<et2-hbox>
|
||||
<et2-select-account id="token_created_by" readonly="true"></et2-select-account>
|
||||
<et2-date-time id="token_created" readonly="true" align="right"></et2-date-time>
|
||||
</et2-hbox>
|
||||
</row>
|
||||
<row disabled="!@token_updated_by">
|
||||
<et2-description value="Last updated"></et2-description>
|
||||
<et2-hbox>
|
||||
<et2-select-account id="token_updated_by" readonly="true"></et2-select-account>
|
||||
<et2-date-time id="token_updated" readonly="true" align="right"></et2-date-time>
|
||||
</et2-hbox>
|
||||
</row>
|
||||
<row>
|
||||
<et2-hbox span="all">
|
||||
<et2-button accesskey="s" label="Save" id="button[save]"></et2-button>
|
||||
<et2-button label="Apply" id="button[apply]"></et2-button>
|
||||
<et2-button label="Cancel" id="button[cancel]" onclick="window.close(); return false;"></et2-button>
|
||||
<et2-button align="right" label="Revoke" id="button[delete]"
|
||||
onclick="et2_dialog.confirm(widget,'Do you really want to revoke this token?','Revoke')"></et2-button>
|
||||
</et2-hbox>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
</template>
|
||||
</overlay>
|
66
admin/templates/default/tokens.xet
Normal file
66
admin/templates/default/tokens.xet
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE overlay PUBLIC "-//EGroupware GmbH//eTemplate 2.0//EN" "https://www.egroupware.org/etemplate2.0.dtd">
|
||||
<overlay>
|
||||
<template id="admin.tokens.rows" template="" lang="" group="0" version="1.9.001">
|
||||
<grid width="100%" height="100%">
|
||||
<columns>
|
||||
<column width="30"/>
|
||||
<column width="120"/> <!-- User / All user -->
|
||||
<column width="25%"/> <!-- Applications -->
|
||||
<column width="120"/> <!-- Expiration -->
|
||||
<column width="120"/> <!-- Revoked / By -->
|
||||
<column width="120"/> <!-- Created / By -->
|
||||
<column width="120"/> <!-- Updated / By -->
|
||||
<column width="30%"/> <!-- remark -->
|
||||
</columns>
|
||||
<rows>
|
||||
<row>
|
||||
<nextmatch-sortheader label="ID" id="token_id"/>
|
||||
<et2-nextmatch-header-account id="account_id" emptyLabel="User" accountType="user">
|
||||
<option value="0">All users</option>
|
||||
</et2-nextmatch-header-account>
|
||||
<nextmatch-header id="token_apps" label="Applications"/>
|
||||
<nextmatch-sortheader label="Expiration" id="token_valid_until"/>
|
||||
<et2-vbox>
|
||||
<nextmatch-sortheader label="Revoked" id="token_revoked"/>
|
||||
<et2-nextmatch-header-account label="Revoked by" id="token_revoked_by"></et2-nextmatch-header-account>
|
||||
</et2-vbox>
|
||||
<et2-vbox>
|
||||
<nextmatch-sortheader label="Created" id="token_created"/>
|
||||
<et2-nextmatch-header-account label="Created by" id="token_created_by"></et2-nextmatch-header-account>
|
||||
</et2-vbox>
|
||||
<et2-vbox>
|
||||
<nextmatch-sortheader label="Updated" id="token_created"/>
|
||||
<et2-nextmatch-header-account label="Updated by" id="token_created_by"></et2-nextmatch-header-account>
|
||||
</et2-vbox>
|
||||
<nextmatch-header label="Remark" id="token_remark"/>
|
||||
</row>
|
||||
<row class="$row_cont[class]">
|
||||
<et2-description id="${row}[token_id]" noLang="1"></et2-description>
|
||||
<et2-select-account id="${row}[account_id]" readonly="true"></et2-select-account>
|
||||
<et2-select-app id="${row}[token_apps]" readonly="true" multiple="true"></et2-select-app>
|
||||
<et2-date id="${row}[token_valid_until]" readonly="true"></et2-date>
|
||||
<et2-vbox>
|
||||
<et2-date-time id="${row}[token_revoked]" readonly="true"></et2-date-time>
|
||||
<et2-select-account id="${row}[token_revoked_by]" readonly="true"></et2-select-account>
|
||||
</et2-vbox>
|
||||
<et2-vbox>
|
||||
<et2-date-time id="${row}[token_created]" readonly="true"></et2-date-time>
|
||||
<et2-select-account id="${row}[token_created_by]" readonly="true"></et2-select-account>
|
||||
</et2-vbox>
|
||||
<et2-vbox>
|
||||
<et2-date-time id="${row}[token_updated]" readonly="true"></et2-date-time>
|
||||
<et2-select-account id="${row}[token_updated_by]" readonly="true"></et2-select-account>
|
||||
</et2-vbox>
|
||||
<et2-description id="${row}[token_remark]"></et2-description>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
</template>
|
||||
<template id="admin.tokens.add" template="" lang="" group="0" version="1.9.001">
|
||||
<et2-button label="Add" id="add" onclick="window.open('$cont[add_link]','_blank','dependent=yes,width=600,height=380,scrollbars=yes,status=yes'); return false;" noSubmit="true"></et2-button>
|
||||
</template>
|
||||
<template id="admin.tokens" template="" lang="" group="0" version="1.9.001">
|
||||
<nextmatch id="nm" template="admin.tokens.rows" header_left="admin.tokens.add"/>
|
||||
</template>
|
||||
</overlay>
|
@ -11,7 +11,7 @@
|
||||
/* Basic information about this app */
|
||||
$setup_info['api']['name'] = 'api';
|
||||
$setup_info['api']['title'] = 'EGroupware API';
|
||||
$setup_info['api']['version'] = '23.1.001';
|
||||
$setup_info['api']['version'] = '23.1.002';
|
||||
$setup_info['api']['versions']['current_header'] = '1.29';
|
||||
// maintenance release in sync with changelog in doc/rpm-build/debian.changes
|
||||
$setup_info['api']['versions']['maintenance_release'] = '23.1.20230620';
|
||||
@ -139,4 +139,4 @@ $setup_info['groupdav']['author'] = $setup_info['groupdav']['maintainer'] = arra
|
||||
);
|
||||
$setup_info['groupdav']['license'] = 'GPL';
|
||||
$setup_info['groupdav']['hooks']['preferences'] = 'EGroupware\\Api\\CalDAV\\Hooks::menus';
|
||||
$setup_info['groupdav']['hooks']['settings'] = 'EGroupware\\Api\\CalDAV\\Hooks::settings';
|
||||
$setup_info['groupdav']['hooks']['settings'] = 'EGroupware\\Api\\CalDAV\\Hooks::settings';
|
||||
|
@ -528,7 +528,7 @@ $phpgw_baseline = array(
|
||||
),
|
||||
'egw_tokens' => array(
|
||||
'fd' => array(
|
||||
'token_id' => array('type' => 'int','precision' => '4','nullable' => False),
|
||||
'token_id' => array('type' => 'auto','precision' => '4','nullable' => False),
|
||||
'account_id' => array('type' => 'int','meta' => 'user','precision' => '4','nullable' => False,'comment' => '0=all users'),
|
||||
'token_hash' => array('type' => 'ascii','precision' => '128','nullable' => False,'comment' => 'hash of token'),
|
||||
'token_limits' => array('type' => 'ascii','meta' => 'json','precision' => '4096','comment' => 'limit run rights of session'),
|
||||
@ -537,11 +537,13 @@ $phpgw_baseline = array(
|
||||
'token_valid_until' => array('type' => 'timestamp'),
|
||||
'token_revoked' => array('type' => 'timestamp'),
|
||||
'token_revoked_by' => array('type' => 'int','meta' => 'user','precision' => '4'),
|
||||
'token_remark' => array('type' => 'varchar','precision' => 255)
|
||||
'token_remark' => array('type' => 'varchar','precision' => '1024'),
|
||||
'token_updated' => array('type' => 'timestamp'),
|
||||
'token_updated_by' => array('type' => 'int','meta' => 'user','precision' => '4')
|
||||
),
|
||||
'pk' => array('token_id'),
|
||||
'fk' => array(),
|
||||
'ix' => array('account_id'),
|
||||
'uc' => array()
|
||||
)
|
||||
);
|
||||
);
|
||||
|
@ -859,7 +859,7 @@ function api_upgrade23_1()
|
||||
{
|
||||
$GLOBALS['egw_setup']->oProc->CreateTable('egw_tokens',array(
|
||||
'fd' => array(
|
||||
'token_id' => array('type' => 'int','precision' => '4','nullable' => False),
|
||||
'token_id' => array('type' => 'auto','precision' => '4','nullable' => False),
|
||||
'account_id' => array('type' => 'int','meta' => 'user','precision' => '4','nullable' => False,'comment' => '0=all users'),
|
||||
'token_hash' => array('type' => 'ascii','precision' => '128','nullable' => False,'comment' => 'hash of token'),
|
||||
'token_limits' => array('type' => 'ascii','meta' => 'json','precision' => '4096','comment' => 'limit run rights of session'),
|
||||
@ -868,7 +868,9 @@ function api_upgrade23_1()
|
||||
'token_valid_until' => array('type' => 'timestamp'),
|
||||
'token_revoked' => array('type' => 'timestamp'),
|
||||
'token_revoked_by' => array('type' => 'int','meta' => 'user','precision' => '4'),
|
||||
'token_remark' => array('type' => 'varchar','precision' => 255)
|
||||
'token_remark' => array('type' => 'varchar','precision' => 1024),
|
||||
'token_updated' => array('type' => 'timestamp'),
|
||||
'token_updated_by' => array('type' => 'int','meta' => 'user','precision' => '4')
|
||||
),
|
||||
'pk' => array('token_id'),
|
||||
'fk' => array(),
|
||||
@ -876,5 +878,28 @@ function api_upgrade23_1()
|
||||
'uc' => array()
|
||||
));
|
||||
|
||||
return $GLOBALS['setup_info']['api']['currentver'] = '23.1.001';
|
||||
return $GLOBALS['setup_info']['api']['currentver'] = '23.1.002';
|
||||
}
|
||||
|
||||
function api_upgrade23_1_001()
|
||||
{
|
||||
$GLOBALS['egw_setup']->oProc->AlterColumn('egw_tokens','token_id',array(
|
||||
'type' => 'auto',
|
||||
'precision' => '4',
|
||||
'nullable' => False
|
||||
));
|
||||
$GLOBALS['egw_setup']->oProc->AlterColumn('egw_tokens','token_remark',array(
|
||||
'type' => 'varchar',
|
||||
'precision' => '1024'
|
||||
));
|
||||
$GLOBALS['egw_setup']->oProc->AddColumn('egw_tokens','token_updated',array(
|
||||
'type' => 'timestamp'
|
||||
));
|
||||
$GLOBALS['egw_setup']->oProc->AddColumn('egw_tokens','token_updated_by',array(
|
||||
'type' => 'int',
|
||||
'meta' => 'user',
|
||||
'precision' => '4'
|
||||
));
|
||||
|
||||
return $GLOBALS['setup_info']['api']['currentver'] = '23.1.002';
|
||||
}
|
@ -33,6 +33,7 @@ class Token extends APi\Storage\Base
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(self::APP, self::TABLE, null, '', true, 'object');
|
||||
$this->convert_all_timestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,55 +43,202 @@ class Token extends APi\Storage\Base
|
||||
* @param string $token must start with "token<token_id>:", or function will return null
|
||||
* @param ?array& $limits on return limits of token
|
||||
* @return bool|null null: $token is no token, probably a password, false: invalid token, true: valid token for $user
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function authenticate(string $user, string $token, array& $limits=null)
|
||||
{
|
||||
if (!preg_match(self::TOKEN_REGEXP, $token, $matches))
|
||||
{
|
||||
return null; // no a token
|
||||
return null; // not a token
|
||||
}
|
||||
if (!($data = self::getInstance()->read([
|
||||
try {
|
||||
$data = self::getInstance()->read([
|
||||
'token_id' => $matches[1],
|
||||
'account_id' => [0, Api\Accounts::getInstance()->name2id($user)],
|
||||
'token_revoked' => null,
|
||||
'(token_valid_until IS NULL OR token_valid_until > NOW())'
|
||||
])) || !password_verify($matches[2], $data['token_hash']))
|
||||
{
|
||||
return false; // wrong/invalid token
|
||||
]);
|
||||
if (!password_verify($matches[2], $data['token_hash']))
|
||||
{
|
||||
return false; // invalid token password
|
||||
}
|
||||
$limits = $data['token_limits'];
|
||||
return true;
|
||||
}
|
||||
catch (Api\Exception\NotFound $e) {
|
||||
return false; // token not found
|
||||
}
|
||||
$limits = $data['token_limits'] ? json_decode($data['token_limits'], true) : null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a token and return it
|
||||
*
|
||||
* @param int $account_id
|
||||
* @param ?DateTime $until
|
||||
* @param ?\DateTimeInterface $until
|
||||
* @param ?string $remark
|
||||
* @param ?array $limits app-name => rights pairs, run rights are everything evaluation to true,
|
||||
* the rights can be an array with more granulate rights, but the app needs to check this itself!
|
||||
* @return string
|
||||
* @return array full token record plus token under key "token"
|
||||
* @throws Api\Exception\NoPermission\Admin if non-admin user tries to create token for anyone else
|
||||
* @throws Api\Exception\NotFound if token_id does NOT exist
|
||||
* @throws Api\Db\Exception if token could not be stored
|
||||
*/
|
||||
public static function create(int $account_id, DateTime $until=null, string $remark=null, array $limits=null): string
|
||||
public static function create(int $account_id, \DateTimeInterface $until=null, string $remark=null, array $limits=null): array
|
||||
{
|
||||
if (empty($GLOBALS['egw_info']['user']['apps']['admin']))
|
||||
{
|
||||
$account_id = $GLOBALS['egw_info']['user']['account_id'];
|
||||
}
|
||||
$token = Api\Auth::randomstring(16);
|
||||
$inst = self::getInstance();
|
||||
$inst->init([
|
||||
'account_id' => $account_id,
|
||||
'token_hash' => password_hash($token, PASSWORD_DEFAULT),
|
||||
'token_created' => new Api\DateTime(),
|
||||
'token_created_by' => $GLOBALS['egw_info']['user']['account_id'],
|
||||
'token_valid_until' => $until,
|
||||
'token_remark' => $remark,
|
||||
'token_limits' => $limits ? json_encode($limits) : null,
|
||||
'token_limits' => $limits,
|
||||
]);
|
||||
if (!($token_id = $inst->save()))
|
||||
$inst->save();
|
||||
|
||||
return $inst->data+[
|
||||
'token' => self::PREFIX.$inst->data['token_id'].'_'.$token,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke or (re-)activate a token
|
||||
*
|
||||
* @param int $token_id
|
||||
* @param bool $revoke true: revoke, false: (re-)activate
|
||||
* @throws Api\Exception\NoPermission\Admin if non-admin user tries to create token for anyone else
|
||||
* @throws Api\Exception\NotFound if token_id does NOT exist
|
||||
* @throws Api\Db\Exception if token could not be stored
|
||||
*/
|
||||
public static function revoke(int $token_id, bool $revoke=true)
|
||||
{
|
||||
$inst = self::getInstance();
|
||||
$inst->read($token_id);
|
||||
return $inst->save([
|
||||
'token_revoked_by' => $GLOBALS['egw_info']['user']['account_id'],
|
||||
'token_revoked' => $revoke ? $inst->now : null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* saves the content of data to the db
|
||||
*
|
||||
* @param array $keys =null if given $keys are copied to data before saveing => allows a save as
|
||||
* @param string|array $extra_where =null extra where clause, eg. to check an etag, returns true if no affected rows!
|
||||
* @return int|boolean 0 on success, or errno != 0 on error, or true if $extra_where is given and no rows affected
|
||||
* @throws Api\Exception\NoPermission\Admin if non-admin user tries to create token for anyone else
|
||||
* @throws Api\Exception\NotFound if token_id does NOT exist
|
||||
* @throws Api\Db\Exception if token could not be stored
|
||||
*/
|
||||
function save($keys=null,$extra_where=null)
|
||||
{
|
||||
if (is_array($keys) && count($keys)) $this->data_merge($keys);
|
||||
|
||||
if (empty($GLOBALS['egw_info']['user']['apps']['admin']) && $this->data['account_id'] != $GLOBALS['egw_info']['user']['account_id'])
|
||||
{
|
||||
throw new Api\Exception('Error storing token');
|
||||
throw new Api\Exception\NoPermission\Admin();
|
||||
}
|
||||
return self::PREFIX.$token_id.'_'.$token;
|
||||
|
||||
if (empty($this->data['token_id']))
|
||||
{
|
||||
$this->data['token_created_by'] = $GLOBALS['egw_info']['user']['account_id'];
|
||||
$this->data['token_created'] = $this->now;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->data['token_updated_by'] = $GLOBALS['egw_info']['user']['account_id'];
|
||||
$this->data['token_updated'] = $this->now;
|
||||
}
|
||||
if (($ret = parent::save(null, $extra_where)))
|
||||
{
|
||||
throw new Api\Db\Exception(lang('Error storing token'));
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* reads row matched by key and puts all cols in the data array
|
||||
*
|
||||
* @param array $keys array with keys in form internalName => value, may be a scalar value if only one key
|
||||
* @param string|array $extra_cols ='' string or array of strings to be added to the SELECT, eg. "count(*) as num"
|
||||
* @param string $join ='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or
|
||||
* @return array data if row could be retrieved
|
||||
* @throws Api\Exception\NotFound if entry was NOT found
|
||||
*/
|
||||
function read($keys,$extra_cols='',$join='')
|
||||
{
|
||||
if (!($data = parent::read($keys, $extra_cols, $join)))
|
||||
{
|
||||
throw new Api\Exception\NotFound();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert limits to allowed apps
|
||||
*
|
||||
* @param array|null $limits
|
||||
* @return array of app-names
|
||||
*/
|
||||
public static function limits2apps(array $limits=null): array
|
||||
{
|
||||
return $limits ? array_keys(array_filter($limits)) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert apps to (default, value === true) limits
|
||||
*
|
||||
* @param array $apps
|
||||
* @return array|null
|
||||
*/
|
||||
public static function apps2limits(array $apps): ?array
|
||||
{
|
||||
return $apps ? array_combine($apps, array_fill(0, count($apps), true)) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the data from the db-format to your work-format
|
||||
*
|
||||
* @param array $data =null if given works on that array and returns result, else works on internal data-array
|
||||
* @return array
|
||||
*/
|
||||
function db2data($data=null)
|
||||
{
|
||||
if (($intern = !is_array($data)))
|
||||
{
|
||||
$data =& $this->data;
|
||||
}
|
||||
|
||||
if (is_string($data['token_limits']))
|
||||
{
|
||||
$data['token_limits'] = json_decode($data['token_limits'], true);
|
||||
}
|
||||
|
||||
return parent::db2data($intern ? null : $data); // important to use null, if $intern!
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the data from your work-format to the db-format
|
||||
*
|
||||
* @param array $data =null if given works on that array and returns result, else works on internal data-array
|
||||
* @return array
|
||||
*/
|
||||
function data2db($data=null)
|
||||
{
|
||||
if (($intern = !is_array($data)))
|
||||
{
|
||||
$data =& $this->data;
|
||||
}
|
||||
|
||||
if (is_array($data['token_limits']))
|
||||
{
|
||||
$data['token_limits'] = $data['token_limits'] ? json_encode($data['token_limits']) : null;
|
||||
}
|
||||
|
||||
return parent::data2db($intern ? null : $data);
|
||||
}
|
||||
|
||||
private static self $instance;
|
||||
|
Loading…
Reference in New Issue
Block a user