diff --git a/app/config/config.yml.dist b/app/config/config.yml.dist index b8af280..b988285 100644 --- a/app/config/config.yml.dist +++ b/app/config/config.yml.dist @@ -6,6 +6,7 @@ security: login_required_to_view_gist: false login_required_to_view_embeded_gist: false api: + enabled: true base_url: 'https://gist.deblan.org/' data: path: data/git diff --git a/app/config/routing.yml b/app/config/routing.yml index 666ca81..e3421cf 100644 --- a/app/config/routing.yml +++ b/app/config/routing.yml @@ -57,10 +57,14 @@ revisions: path: /revs/{gist} defaults: {_controller: Gist\Controller\ViewController::revisionsAction, _locale: en} +api_list: + path: /api/list/{apiKey} + defaults: {_controller: Gist\Controller\ApiController::listAction, _locale: en} + api_create: - path: /api/create + path: /api/create/{apiKey} defaults: {_controller: Gist\Controller\ApiController::createAction, _locale: en} api_update: - path: /api/update/{gist} + path: /api/update/{gist}/{apiKey} defaults: {_controller: Gist\Controller\ApiController::updateAction, _locale: en} diff --git a/src/Gist/Api/Client.php b/src/Gist/Api/Client.php index b3055a5..830416f 100644 --- a/src/Gist/Api/Client.php +++ b/src/Gist/Api/Client.php @@ -12,25 +12,26 @@ use GuzzleHttp\Client as BaseClient; class Client extends BaseClient { /** - * URI of creation - * + * URI of creation. + * * @const string */ const CREATE = '/en/api/create'; /** - * URI of update + * URI of update. * * @const string */ const UPDATE = '/en/api/update/{gist}'; /** - * Creates a gist + * Creates a gist. * * @param string $title The title * @param string $type The type * @param string $content The content + * * @return array */ public function create($title, $type, $content) diff --git a/src/Gist/Controller/ApiController.php b/src/Gist/Controller/ApiController.php index 1fd08d9..a27ac1a 100644 --- a/src/Gist/Controller/ApiController.php +++ b/src/Gist/Controller/ApiController.php @@ -8,6 +8,8 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Gist\Form\ApiCreateGistForm; use Gist\Model\GistQuery; use Gist\Form\ApiUpdateGistForm; +use GitWrapper\GitException; +use Gist\Model\UserQuery; /** * Class ApiController. @@ -17,16 +19,73 @@ use Gist\Form\ApiUpdateGistForm; class ApiController extends Controller { /** - * Creates a gist. + * Lists gists. * * @param Request $request + * @param string $apiKey * * @return JsonResponse */ - public function createAction(Request $request) + public function listAction(Request $request, $apiKey) { $app = $this->getApp(); + if (false === $app['settings']['api']['enabled']) { + return new Response('', 403); + } + + if (false === $this->isValidApiKey($apiKey)) { + return $this->invalidApiKeyResponse(); + } + + if (false === $request->isMethod('get')) { + return $this->invalidMethodResponse('GET method is required.'); + } + + $gists = GistQuery::create()->find(); + $data = array(); + + foreach ($gists as $gist) { + try { + $history = $app['gist']->getHistory($gist); + + $value = $gist->toArray(); + $value['url'] = $request->getSchemeAndHttpHost().$app['url_generator']->generate( + 'view', + array( + 'gist' => $gist->getFile(), + 'commit' => array_pop($history)['commit'], + ) + ); + + $data[] = $value; + } catch (GitException $e) { + } + } + + return new JsonResponse($data); + } + + /** + * Creates a gist. + * + * @param Request $request + * @param string $apiKey + * + * @return JsonResponse + */ + public function createAction(Request $request, $apiKey) + { + $app = $this->getApp(); + + if (false === $app['settings']['api']['enabled']) { + return new Response('', 403); + } + + if (false === $this->isValidApiKey($apiKey)) { + return $this->invalidApiKeyResponse(); + } + if (false === $request->isMethod('post')) { return $this->invalidMethodResponse('POST method is required.'); } @@ -48,16 +107,16 @@ class ApiController extends Controller $history = $app['gist']->getHistory($gist); - return new JsonResponse(array( - 'url' => $request->getSchemeAndHttpHost().$app['url_generator']->generate( - 'view', - array( - 'gist' => $gist->getFile(), - 'commit' => array_pop($history)['commit'], - ) - ), - 'gist' => $gist->toArray(), - )); + $data = $gist->toArray(); + $data['url'] = $request->getSchemeAndHttpHost().$app['url_generator']->generate( + 'view', + array( + 'gist' => $gist->getFile(), + 'commit' => array_pop($history)['commit'], + ) + ); + + return new JsonResponse($data); } return $this->invalidRequestResponse('Invalid field(s)'); @@ -67,14 +126,23 @@ class ApiController extends Controller * Updates a gist. * * @param Request $request - * @param string $gist + * @param string $gist + * @param string $apiKey * * @return JsonResponse */ - public function updateAction(Request $request, $gist) + public function updateAction(Request $request, $gist, $apiKey) { $app = $this->getApp(); + if (false === $app['settings']['api']['enabled']) { + return new Response('', 403); + } + + if (false === $this->isValidApiKey($apiKey)) { + return $this->invalidApiKeyResponse(); + } + if (false === $request->isMethod('post')) { return $this->invalidMethodResponse('POST method is required.'); } @@ -106,21 +174,38 @@ class ApiController extends Controller $history = $app['gist']->getHistory($gist); - return new JsonResponse(array( - 'url' => $request->getSchemeAndHttpHost().$app['url_generator']->generate( - 'view', - array( - 'gist' => $gist->getFile(), - 'commit' => array_pop($history)['commit'], - ) - ), - 'gist' => $gist->toArray(), - )); + $data = $gist->toArray(); + $data['url'] = $request->getSchemeAndHttpHost().$app['url_generator']->generate( + 'view', + array( + 'gist' => $gist->getFile(), + 'commit' => array_pop($history)['commit'], + ) + ); + + return new JsonResponse($data); } return $this->invalidRequestResponse('Invalid field(s)'); } + /** + * Builds an invalid api key response. + * + * @param mixed $message + * + * @return JsonResponse + */ + protected function invalidApiKeyResponse() + { + $data = [ + 'error' => ' Unauthorized', + 'message' => 'Invalid API KEY', + ]; + + return new JsonResponse($data, 401); + } + /** * Builds an invalid method response. * @@ -154,4 +239,11 @@ class ApiController extends Controller return new JsonResponse($data, 400); } + + protected function isValidApiKey($apiKey) + { + return !empty($apiKey) && UserQuery::create() + ->filterByApiKey($apiKey) + ->count() === 1; + } } diff --git a/src/Gist/Controller/LoginController.php b/src/Gist/Controller/LoginController.php index a45d1a3..f835306 100644 --- a/src/Gist/Controller/LoginController.php +++ b/src/Gist/Controller/LoginController.php @@ -26,7 +26,7 @@ class LoginController extends Controller { $app = $this->getApp(); - if (false === $app['settings']['enable_registration']) { + if (false === $app['settings']['security']['enable_registration']) { return new Response('', 403); } @@ -78,7 +78,7 @@ class LoginController extends Controller { $app = $this->getApp(); - if (false === $app['settings']['enable_login']) { + if (false === $app['settings']['security']['enable_login']) { return new Response('', 403); } diff --git a/src/Gist/Controller/MyController.php b/src/Gist/Controller/MyController.php index aa113c8..375bdd2 100644 --- a/src/Gist/Controller/MyController.php +++ b/src/Gist/Controller/MyController.php @@ -58,6 +58,24 @@ class MyController extends Controller $gists = $this->getUser()->getGistsPager($page, $options); + $apiKey = $this->getUser()->getApiKey(); + + if (empty($apiKey)) { + $regenerateApiKey = true; + } elseif ($request->request->get('apiKey') === $apiKey && $request->request->has('generateApiKey')) { + $regenerateApiKey = true; + } else { + $regenerateApiKey = false; + } + + if ($regenerateApiKey) { + $apiKey = $app['salt_generator']->generate(32, true); + + $this->getUser() + ->setApiKey($apiKey) + ->save(); + } + if ($request->isMethod('post')) { $deleteForm->handleRequest($request); $passwordForm->handleRequest($request); @@ -104,6 +122,7 @@ class MyController extends Controller array( 'gists' => $gists, 'page' => $page, + 'apiKey' => $apiKey, 'deleteForm' => $deleteForm->createView(), 'filterForm' => $filterForm->createView(), 'passwordForm' => $passwordForm->createView(), diff --git a/src/Gist/Model/Gist.php b/src/Gist/Model/Gist.php index 23a4809..86efe42 100644 --- a/src/Gist/Model/Gist.php +++ b/src/Gist/Model/Gist.php @@ -3,6 +3,7 @@ namespace Gist\Model; use Gist\Model\Base\Gist as BaseGist; +use Propel\Runtime\Map\TableMap; /** * Class Gist. @@ -86,4 +87,25 @@ class Gist extends BaseGist return $this; } + + /** + * {@inheritdoc} + */ + public function toArray($keyType = TableMap::TYPE_PHPNAME, $includeLazyLoadColumns = true, $alreadyDumpedObjects = array(), $includeForeignObjects = false) + { + $data = parent::toArray( + $keyType, + $includeLazyLoadColumns, + $alreadyDumpedObjects, + $includeForeignObjects + ); + + foreach ($data as $key => $value) { + $newKey = lcfirst($key); + unset($data[$key]); + $data[$newKey] = $value; + } + + return $data; + } } diff --git a/src/Gist/Model/User.php b/src/Gist/Model/User.php index e5d483b..b57d03e 100644 --- a/src/Gist/Model/User.php +++ b/src/Gist/Model/User.php @@ -54,7 +54,7 @@ class User extends BaseUser implements UserInterface * * @return Propel\Runtime\Util\PropelModelPager */ - public function getGistsPager($page, $options = array(), $maxPerPage = 10) + public function getGistsPager($page, $options = array(), $maxPerPage = 10) { $query = GistQuery::create() ->filterByUser($this) @@ -63,11 +63,11 @@ class User extends BaseUser implements UserInterface if (!empty($options['type']) && $options['type'] !== 'all') { $query->filterByType($options['type']); } - + if (!empty($options['title'])) { $query->filterByTitle('%'.$options['title'].'%', Criteria::LIKE); } - + if (!empty($options['cipher']) && $options['cipher'] !== 'anyway') { $bools = array( 'yes' => true, diff --git a/src/Gist/Resources/config/propel/schema.xml b/src/Gist/Resources/config/propel/schema.xml index ce79ebb..8caac4e 100644 --- a/src/Gist/Resources/config/propel/schema.xml +++ b/src/Gist/Resources/config/propel/schema.xml @@ -22,6 +22,7 @@ + diff --git a/src/Gist/Resources/views/My/my.html.twig b/src/Gist/Resources/views/My/my.html.twig index 2e65a3b..9f954bb 100644 --- a/src/Gist/Resources/views/My/my.html.twig +++ b/src/Gist/Resources/views/My/my.html.twig @@ -230,6 +230,24 @@ + + {% if app.settings.api.enabled %} +
+
+ {{ 'api.title'|trans }} +
+
+
+
+

+ + +

+
+
+
+
+ {% endif %} {% endblock %} diff --git a/src/Gist/Resources/views/base.html.twig b/src/Gist/Resources/views/base.html.twig index 046d5f1..9146d00 100644 --- a/src/Gist/Resources/views/base.html.twig +++ b/src/Gist/Resources/views/base.html.twig @@ -1,6 +1,6 @@ {% set theme_settings = app.settings.theme %} -{% set security_dettings = app.settings.security %} +{% set security_settings = app.settings.security %} {% block css %} @@ -56,14 +56,14 @@ {{ 'app.menu.my.logout.title'|trans }} - {% elseif security_dettings.enable_login %} + {% elseif security_settings.enable_login %}
  • {{ 'app.menu.my.login.title'|trans }}
  • - {% if security_dettings.enable_registration %} + {% if security_settings.enable_registration %}
  • {{ 'app.menu.my.register.title'|trans }} diff --git a/src/Gist/Service/SaltGenerator.php b/src/Gist/Service/SaltGenerator.php index f86a0e0..a3143c2 100644 --- a/src/Gist/Service/SaltGenerator.php +++ b/src/Gist/Service/SaltGenerator.php @@ -18,18 +18,30 @@ class SaltGenerator * * @return string */ - public function generate($length = 32) + public function generate($length = 32, $isApiKey = false) { if (!is_numeric($length)) { throw new InvalidArgumentException('Paramter length must be a valid integer.'); } if (function_exists('openssl_random_pseudo_bytes')) { - return substr(base64_encode(openssl_random_pseudo_bytes($length)), 0, $length); + $string = base64_encode(openssl_random_pseudo_bytes(256)); } if (function_exists('mcrypt_create_iv')) { - return substr(base64_encode(mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)), 0, $length); + $string = base64_encode(mcrypt_create_iv(256, MCRYPT_DEV_URANDOM)); + } + + if (!empty($string)) { + if (true === $isApiKey) { + $string = str_replace( + array('+', '%', '/', '#', '&'), + '', + $string + ); + } + + return substr($string, 0, $length); } throw new RuntimeException('You must enable openssl or mcrypt modules.'); diff --git a/src/Gist/Service/UserProvider.php b/src/Gist/Service/UserProvider.php index beab8e1..b0f1813 100644 --- a/src/Gist/Service/UserProvider.php +++ b/src/Gist/Service/UserProvider.php @@ -126,6 +126,7 @@ class UserProvider implements UserProviderInterface $user ->setRoles('ROLE_USER') ->setPassword($this->encoder->encodePassword($password, $user->getSalt())) + ->setApiKey($this->saltGenerator->generate(32, true)) ->save(); return $user; diff --git a/web/app/js/app.js b/web/app/js/app.js index a4d64d4..671d102 100644 --- a/web/app/js/app.js +++ b/web/app/js/app.js @@ -98,6 +98,10 @@ var myEvents = function() { $('#form-deletion form').submit(); } }); + + $(document).on('change keyup keydown', '#form-api-key', function() { + $(this).val($(this).data('key')); + }); } var mainEditorEvents = function() {