1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-09 20:52:56 +01:00

Added support for API tokens

This commit is contained in:
Hillel Coren 2015-03-03 17:32:50 +02:00
parent 29833e6d83
commit 9210e3d578
22 changed files with 447 additions and 5 deletions

View File

@ -87,6 +87,8 @@ class AccountController extends \BaseController
if ($entityType == 'user') {
return Redirect::to('company/'.ACCOUNT_ADVANCED_SETTINGS.'/'.ACCOUNT_USER_MANAGEMENT);
} elseif ($entityType == 'token') {
return Redirect::to('company/'.ACCOUNT_ADVANCED_SETTINGS.'/'.ACCOUNT_TOKEN_MANAGEMENT);
} else {
return Redirect::to("{$entityType}s");
}

View File

@ -0,0 +1,159 @@
<?php
/*
|--------------------------------------------------------------------------
| Confide Controller Template
|--------------------------------------------------------------------------
|
| This is the default Confide controller template for controlling user
| authentication. Feel free to change to your needs.
|
*/
use ninja\repositories\AccountRepository;
class TokenController extends BaseController
{
public function getDatatable()
{
$query = DB::table('account_tokens')
->where('account_tokens.account_id', '=', Auth::user()->account_id);
if (!Session::get('show_trash:token')) {
$query->where('account_tokens.deleted_at', '=', null);
}
$query->select('account_tokens.public_id', 'account_tokens.name', 'account_tokens.token', 'account_tokens.public_id', 'account_tokens.deleted_at');
return Datatable::query($query)
->addColumn('name', function ($model) { return link_to('tokens/'.$model->public_id.'/edit', $model->name); })
->addColumn('token', function ($model) { return $model->token; })
->addColumn('dropdown', function ($model) {
$actions = '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
if (!$model->deleted_at) {
$actions .= '<li><a href="'.URL::to('tokens/'.$model->public_id).'/edit">'.uctrans('texts.edit_token').'</a></li>
<li class="divider"></li>
<li><a href="javascript:deleteToken('.$model->public_id.')">'.uctrans('texts.delete_token').'</a></li>';
}
$actions .= '</ul>
</div>';
return $actions;
})
->orderColumns(['name', 'token'])
->make();
}
public function edit($publicId)
{
$token = AccountToken::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $publicId)->firstOrFail();
$data = [
'showBreadcrumbs' => false,
'token' => $token,
'method' => 'PUT',
'url' => 'tokens/'.$publicId,
'title' => trans('texts.edit_token'),
];
return View::make('accounts.token', $data);
}
public function update($publicId)
{
return $this->save($publicId);
}
public function store()
{
return $this->save();
}
/**
* Displays the form for account creation
*
*/
public function create()
{
if (!Auth::user()->confirmed) {
Session::flash('error', trans('texts.register_to_add_user'));
return Redirect::to('company/advanced_settings/user_management');
}
$data = [
'showBreadcrumbs' => false,
'token' => null,
'method' => 'POST',
'url' => 'tokens',
'title' => trans('texts.add_token'),
];
return View::make('accounts.token', $data);
}
public function delete()
{
$tokenPublicId = Input::get('tokenPublicId');
$token = AccountToken::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $tokenPublicId)->firstOrFail();
$token->delete();
Session::flash('message', trans('texts.deleted_token'));
return Redirect::to('company/advanced_settings/token_management');
}
/**
* Stores new account
*
*/
public function save($tokenPublicId = false)
{
$rules = [
'name' => 'required',
];
if ($tokenPublicId) {
$token = AccountToken::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $tokenPublicId)->firstOrFail();
}
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to($tokenPublicId ? 'tokens/edit' : 'tokens/create')->withInput()->withErrors($validator);
}
if ($tokenPublicId) {
$token->name = trim(Input::get('name'));
} else {
$lastToken = AccountToken::withTrashed()->where('account_id', '=', Auth::user()->account_id)
->orderBy('public_id', 'DESC')->first();
$token = AccountToken::createNew();
$token->name = trim(Input::get('name'));
$token->token = str_random(RANDOM_KEY_LENGTH);
$token->public_id = $lastToken ? $lastToken->public_id + 1 : 1;
}
$token->save();
if ($tokenPublicId) {
$message = trans('texts.updated_token');
} else {
$message = trans('texts.created_token');
}
Session::flash('message', $message);
return Redirect::to('company/advanced_settings/token_management');
}
}

View File

@ -0,0 +1,54 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddTokens extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('account_tokens', function($table)
{
$table->increments('id');
$table->unsignedInteger('account_id')->index();
$table->unsignedInteger('user_id');
$table->timestamps();
$table->softDeletes();
$table->string('name')->nullable();
$table->string('token')->unique();
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedInteger('public_id')->nullable();
$table->unique(['account_id', 'public_id']);
});
Schema::table('activities', function($table)
{
$table->unsignedInteger('token_id')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('account_tokens');
Schema::table('activities', function($table)
{
$table->dropColumn('token_id');
});
}
}

View File

@ -176,6 +176,16 @@ Route::filter('auth.basic', function()
Route::filter('api.access', function()
{
$headers = Utils::getApiHeaders();
// check for a valid token
$token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']);
if ($token) {
Auth::loginUsingId($token->user_id);
Session::set('token_id', $token->id);
} else {
return Response::make('Invalid token', 403, $headers);
}
if (!Utils::isPro()) {
return Response::make('API requires pro plan', 403, $headers);

View File

@ -539,4 +539,15 @@ return array(
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
);

View File

@ -529,4 +529,15 @@ return array(
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
);

View File

@ -537,4 +537,15 @@ return array(
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
);

View File

@ -509,4 +509,15 @@ return array(
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
);

View File

@ -530,4 +530,14 @@ return array(
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
);

View File

@ -531,5 +531,15 @@ return array(
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
);

View File

@ -539,6 +539,16 @@ return array(
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
);

View File

@ -538,5 +538,15 @@ return array(
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
);

View File

@ -532,6 +532,16 @@ return array(
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
);

View File

@ -519,5 +519,16 @@ return array(
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
'show_deleted_tokens' => 'Show deleted tokens',
'deleted_token' => 'Successfully deleted token',
'created_token' => 'Successfully created token',
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
);

View File

@ -466,8 +466,9 @@ class Utils
$person = $person ? $person->getDisplayName() : '<i>System</i>';
$entity = $entity ? '['.$entity->getActivityKey().']' : '';
$otherPerson = $otherPerson ? 'to '.$otherPerson->getDisplayName() : '';
$token = Session::get('token_id') ? ' ('.trans('texts.token').')' : '';
return trim("$person $action $entity $otherPerson");
return trim("$person $token $action $entity $otherPerson");
}
public static function decodeActivity($message)

9
app/models/AccountToken.php Executable file
View File

@ -0,0 +1,9 @@
<?php
class AccountToken extends EntityModel
{
public function account()
{
return $this->belongsTo('Account');
}
}

View File

@ -34,6 +34,8 @@ class Activity extends Eloquent
Utils::fatalError();
}
$activity->token_id = Session::get('token_id', null);
return $activity;
}

View File

@ -82,6 +82,10 @@ Route::group(array('before' => 'auth'), function() {
Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation');
Route::get('restore_user/{user_id}', 'UserController@restoreUser');
Route::get('api/tokens', array('as'=>'api.tokens', 'uses'=>'TokenController@getDatatable'));
Route::resource('tokens', 'TokenController');
Route::post('tokens/delete', 'TokenController@delete');
Route::get('api/products', array('as'=>'api.products', 'uses'=>'ProductController@getDatatable'));
Route::resource('products', 'ProductController');
Route::get('products/{product_id}/archive', 'ProductController@archive');
@ -142,7 +146,7 @@ Route::group(array('before' => 'auth'), function() {
});
// Route group for API
Route::group(array('prefix' => 'api/v1', 'before' => ['auth.basic', 'api.access']), function()
Route::group(array('prefix' => 'api/v1', 'before' => ['api.access']), function()
{
Route::resource('ping', 'ClientApiController@ping');
Route::resource('clients', 'ClientApiController');
@ -186,6 +190,7 @@ define('ACCOUNT_CHART_BUILDER', 'chart_builder');
define('ACCOUNT_USER_MANAGEMENT', 'user_management');
define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations');
define('ACCOUNT_EMAIL_TEMPLATES', 'email_templates');
define('ACCOUNT_TOKEN_MANAGEMENT', 'token_management');
define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);

View File

@ -4,7 +4,8 @@
{{ HTML::nav_link('company/advanced_settings/email_templates', 'email_templates') }}
{{ HTML::nav_link('company/advanced_settings/data_visualizations', 'data_visualizations') }}
{{ HTML::nav_link('company/advanced_settings/chart_builder', 'chart_builder') }}
{{ HTML::nav_link('company/advanced_settings/user_management', 'user_management') }}
{{ HTML::nav_link('company/advanced_settings/user_management', 'users') }}
{{ HTML::nav_link('company/advanced_settings/token_management', 'tokens') }}
</ul>
<p>&nbsp;</p>

View File

@ -0,0 +1,29 @@
@extends('accounts.nav')
@section('content')
@parent
{{ Former::open($url)->method($method)->addClass('col-md-8 col-md-offset-2 warn-on-exit')->rules(array(
'name' => 'required',
)); }}
{{ Former::legend($title) }}
<p>&nbsp;</p>
@if ($token)
{{ Former::populate($token) }}
@endif
{{ Former::text('name') }}
<p>&nbsp;</p>
{{ Former::actions(
Button::lg_success_submit(trans('texts.save'))->append_with_icon('floppy-disk'),
Button::lg_default_link('company/advanced_settings/token_management', 'Cancel')->append_with_icon('remove-circle')
) }}
{{ Former::close() }}
@stop

View File

@ -0,0 +1,67 @@
@extends('accounts.nav')
@section('content')
@parent
@include('accounts.nav_advanced')
{{ Former::open('tokens/delete')->addClass('user-form') }}
{{ Former::legend('token_management') }}
<div style="display:none">
{{ Former::text('tokenPublicId') }}
</div>
{{ Former::close() }}
@if (Utils::isPro())
{{ Button::success_link(URL::to('tokens/create'), trans("texts.add_token"), array('class' => 'pull-right'))->append_with_icon('plus-sign') }}
@endif
<!--
<label for="trashed" style="font-weight:normal; margin-left: 10px;">
<input id="trashed" type="checkbox" onclick="setTrashVisible()"
{{ Session::get('show_trash:token') ? 'checked' : ''}}/> {{ trans('texts.show_deleted_tokens')}}
</label>
-->
{{ Datatable::table()
->addColumn(
trans('texts.name'),
trans('texts.token'),
trans('texts.action'))
->setUrl(url('api/tokens/'))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('bAutoWidth', false)
->setOptions('aoColumns', [[ "sWidth"=> "40%" ], [ "sWidth"=> "40%" ], ["sWidth"=> "20%"]])
->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[2]]])
->render('datatable') }}
<script>
window.onDatatableReady = function() {
$('tbody tr').mouseover(function() {
$(this).closest('tr').find('.tr-action').css('visibility','visible');
}).mouseout(function() {
$dropdown = $(this).closest('tr').find('.tr-action');
if (!$dropdown.hasClass('open')) {
$dropdown.css('visibility','hidden');
}
});
}
function setTrashVisible() {
var checked = $('#trashed').is(':checked');
window.location = '{{ URL::to('view_archive/token') }}' + (checked ? '/true' : '/false');
}
function deleteToken(id) {
if (!confirm('Are you sure?')) {
return;
}
$('#tokenPublicId').val(id);
$('form.user-form').submit();
}
</script>
@stop

View File

@ -7,8 +7,6 @@
{{ Former::open('users/delete')->addClass('user-form') }}
{{ Former::legend('user_management') }}
<div style="display:none">
{{ Former::text('userPublicId') }}
</div>