diff options
31 files changed, 572 insertions, 95 deletions
diff --git a/.travis.yml b/.travis.yml index cc70a9d7..edd10551 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: php php: + - "5.6" - "5.5" - "5.4" - "5.3" diff --git a/README.markdown b/README.markdown index bada2914..2f8138b7 100644 --- a/README.markdown +++ b/README.markdown @@ -24,7 +24,7 @@ Features - Tasks with different colors, categories, sub-tasks, attachments, Markdown support for the description - Automatic actions - Users management with a basic privileges separation (administrator or regular user) -- External authentication: Google Account and LDAP/ActiveDirectory +- External authentication: Google and GitHub accounts as well as LDAP/ActiveDirectory - Webhooks to create tasks from an external software - Host anywhere (shared hosting, VPS, Raspberry Pi or localhost) - No external dependencies @@ -62,6 +62,7 @@ Contributors: - Mathgl67: https://github.com/mathgl67 - Matthieu Keller: https://github.com/maggick - Maxime: https://github.com/EpocDotFr +- Moraxy: https://github.com/moraxy - Nala Ginrut: https://github.com/NalaGinrut - Nekohayo: https://github.com/nekohayo - Olivier Maridat: https://github.com/oliviermaridat @@ -103,6 +104,7 @@ Documentation - [LDAP authentication](docs/ldap-authentication.markdown) - [Google authentication](docs/google-authentication.markdown) +- [GitHub authentication](docs/github-authentication.markdown) #### Developers diff --git a/app/Controller/Action.php b/app/Controller/Action.php index 11dc3b29..797bbfa2 100644 --- a/app/Controller/Action.php +++ b/app/Controller/Action.php @@ -33,10 +33,10 @@ class Action extends Base 'available_events' => $this->action->getAvailableEvents(), 'available_params' => $this->action->getAllActionParameters(), 'columns_list' => $this->board->getColumnsList($project['id']), - 'users_list' => $this->project->getUsersList($project['id'], false), + 'users_list' => $this->project->getUsersList($project['id']), 'projects_list' => $this->project->getList(false), 'colors_list' => $this->task->getColors(), - 'categories_list' => $this->category->getList($project['id'], false), + 'categories_list' => $this->category->getList($project['id']), 'menu' => 'projects', 'title' => t('Automatic actions') ))); @@ -64,10 +64,10 @@ class Action extends Base 'values' => $values, 'action_params' => $action->getActionRequiredParameters(), 'columns_list' => $this->board->getColumnsList($project['id']), - 'users_list' => $this->project->getUsersList($project['id'], false), + 'users_list' => $this->project->getUsersList($project['id']), 'projects_list' => $this->project->getList(false), 'colors_list' => $this->task->getColors(), - 'categories_list' => $this->category->getList($project['id'], false), + 'categories_list' => $this->category->getList($project['id']), 'project' => $project, 'menu' => 'projects', 'title' => t('Automatic actions') diff --git a/app/Controller/Base.php b/app/Controller/Base.php index 13fb9b91..25a72f15 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -20,6 +20,7 @@ use Model\LastLogin; * @property \Model\Config $config * @property \Model\File $file * @property \Model\Google $google + * @property \Model\GitHub $gitHub * @property \Model\LastLogin $lastLogin * @property \Model\Ldap $ldap * @property \Model\Project $project diff --git a/app/Controller/Task.php b/app/Controller/Task.php index d44ba268..15482afc 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -162,17 +162,24 @@ class Task extends Base $task['score'] = $task['score'] ?: ''; - $this->response->html($this->template->layout('task_edit', array( - 'values' => $task, - 'errors' => array(), - 'task' => $task, - 'columns_list' => $this->board->getColumnsList($task['project_id']), - 'users_list' => $this->project->getUsersList($task['project_id']), - 'colors_list' => $this->task->getColors(), - 'categories_list' => $this->category->getList($task['project_id']), - 'menu' => 'tasks', - 'title' => t('Edit a task') - ))); + $params = array( + 'values' => $task, + 'errors' => array(), + 'task' => $task, + 'columns_list' => $this->board->getColumnsList($task['project_id']), + 'users_list' => $this->project->getUsersList($task['project_id']), + 'colors_list' => $this->task->getColors(), + 'categories_list' => $this->category->getList($task['project_id']), + 'ajax' => $this->request->isAjax(), + 'menu' => 'tasks', + 'title' => t('Edit a task') + ); + if ($this->request->isAjax()) { + $this->response->html($this->template->load('task_edit', $params)); + } + else { + $this->response->html($this->template->layout('task_edit', $params)); + } } /** @@ -191,7 +198,13 @@ class Task extends Base if ($this->task->update($values)) { $this->session->flash(t('Task updated successfully.')); - $this->response->redirect('?controller=task&action=show&task_id='.$values['id']); + + if ($this->request->getIntegerParam('ajax')) { + $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']); + } + else { + $this->response->redirect('?controller=task&action=show&task_id='.$values['id']); + } } else { $this->session->flashError(t('Unable to update your task.')); @@ -357,13 +370,20 @@ class Task extends Base { $task = $this->getTask(); - $this->response->html($this->taskLayout('task_edit_description', array( - 'values' => $task, - 'errors' => array(), - 'task' => $task, - 'menu' => 'tasks', - 'title' => t('Edit the description') - ))); + $params = array( + 'values' => $task, + 'errors' => array(), + 'task' => $task, + 'ajax' => $this->request->isAjax(), + 'menu' => 'tasks', + 'title' => t('Edit the description') + ); + if ($this->request->isAjax()) { + $this->response->html($this->template->load('task_edit_description', $params)); + } + else { + $this->response->html($this->taskLayout('task_edit_description', $params)); + } } /** @@ -387,7 +407,12 @@ class Task extends Base $this->session->flashError(t('Unable to update your task.')); } - $this->response->redirect('?controller=task&action=show&task_id='.$task['id']); + if ($this->request->getIntegerParam('ajax')) { + $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']); + } + else { + $this->response->redirect('?controller=task&action=show&task_id='.$task['id']); + } } $this->response->html($this->taskLayout('task_edit_description', array( diff --git a/app/Controller/User.php b/app/Controller/User.php index fca33b28..d30c6fd2 100644 --- a/app/Controller/User.php +++ b/app/Controller/User.php @@ -299,4 +299,68 @@ class User extends Base $this->response->redirect('?controller=user'); } + + /** + * GitHub authentication + * + * @access public + */ + public function gitHub() + { + $code = $this->request->getStringParam('code'); + + if ($code) { + $profile = $this->gitHub->getGitHubProfile($code); + + if (is_array($profile)) { + + // If the user is already logged, link the account otherwise authenticate + if ($this->acl->isLogged()) { + + if ($this->gitHub->updateUser($this->acl->getUserId(), $profile)) { + $this->session->flash(t('Your GitHub account was successfully linked to your profile.')); + } + else { + $this->session->flashError(t('Unable to link your GitHub Account.')); + } + + $this->response->redirect('?controller=user'); + } + else if ($this->gitHub->authenticate($profile['id'])) { + $this->response->redirect('?controller=app'); + } + else { + $this->response->html($this->template->layout('user_login', array( + 'errors' => array('login' => t('GitHub authentication failed')), + 'values' => array(), + 'no_layout' => true, + 'title' => t('Login') + ))); + } + } + } + + $this->response->redirect($this->gitHub->getAuthorizationUrl()); + } + + /** + * Unlink a GitHub account + * + * @access public + */ + public function unlinkGitHub() + { + $this->checkCSRFParam(); + + $this->gitHub->revokeGitHubAccess(); + + if ($this->gitHub->unlink($this->acl->getUserId())) { + $this->session->flash(t('Your GitHub account is no longer linked to your profile.')); + } + else { + $this->session->flashError(t('Unable to unlink your GitHub Account.')); + } + + $this->response->redirect('?controller=user'); + } } diff --git a/app/Locales/de_DE/translations.php b/app/Locales/de_DE/translations.php index 48c7ee16..734c3437 100644 --- a/app/Locales/de_DE/translations.php +++ b/app/Locales/de_DE/translations.php @@ -5,9 +5,9 @@ return array( 'German' => 'Deutsch', 'French' => 'Französisch', 'Polish' => 'Polnisch', - 'Portuguese (Brazilian)' => 'Portugisisch (Brasilien)', + 'Portuguese (Brazilian)' => 'Portugiesisch (Brasilien)', 'Spanish' => 'Spanisch', - 'Chinese (Simplified)' => 'Chinesisch', + 'Chinese (Simplified)' => 'Chinesisch (vereinfacht)', 'None' => 'Kein', 'edit' => 'bearbeiten', 'Edit' => 'Bearbeiten', @@ -27,16 +27,16 @@ return array( 'Grey' => 'Grau', 'Save' => 'Speichern', 'Login' => 'Anmelden', - 'Official website:' => 'Offizielle Website :', + 'Official website:' => 'Offizielle Website:', 'Unassigned' => 'Nicht zugeordnet', 'View this task' => 'Aufgabe ansehen', 'Remove user' => 'Benutzer löschen', - 'Do you really want to remove this user: "%s"?' => 'Willst Du diesen Benutzer wirklich löschen : « %s » ?', + 'Do you really want to remove this user: "%s"?' => 'Willst Du diesen Benutzer wirklich löschen: «%s»?', 'New user' => 'Neuer Benutzer', 'All users' => 'Alle Benutzer', 'Username' => 'Benutzername', 'Password' => 'Passwort', - 'Default Project' => 'Standard Projekt', + 'Default Project' => 'Standardprojekt', 'Administrator' => 'Administrator', 'Sign in' => 'Anmelden', 'Users' => 'Benutzer', @@ -70,37 +70,37 @@ return array( 'Disable' => 'Deaktivieren', 'Enable' => 'Aktivieren', 'New project' => 'Neues Projekt', - 'Do you really want to remove this project: "%s"?' => 'Möchtest Du dieses Projekt wirklich löschen : « %s » ?', + 'Do you really want to remove this project: "%s"?' => 'Möchtest Du dieses Projekt wirklich löschen: «%s»?', 'Remove project' => 'Projekt löschen', 'Boards' => 'Tafel', - 'Edit the board for "%s"' => 'Modifier le tableau pour « %s »', + 'Edit the board for "%s"' => 'Tafel für «%s» bearbeiten', 'All projects' => 'Alle Projekte', 'Change columns' => 'Spalten ändern', 'Add a new column' => 'Neue Spalte hinzufügen', 'Title' => 'Titel', 'Add Column' => 'Neue Spalte', - 'Project "%s"' => 'Projekt « %s »', + 'Project "%s"' => 'Projekt «%s»', 'Nobody assigned' => 'Kein Zuständiger zugeordnet', 'Assigned to %s' => 'Zuständiger: %s', 'Remove a column' => 'Spalte löschen', 'Remove a column from a board' => 'Eine Spalte von einer Tafel löschen', 'Unable to remove this column.' => 'Löschen dieser Spalte nicht möglich.', - 'Do you really want to remove this column: "%s"?' => 'Willst Du diese Spalte wirklich löschen : « %s » ?', - 'This action will REMOVE ALL TASKS associated to this column!' => 'Diese Aktion wird ALLE AUFGABEN löschen !', + 'Do you really want to remove this column: "%s"?' => 'Willst Du diese Spalte wirklich löschen: «%s»?', + 'This action will REMOVE ALL TASKS associated to this column!' => 'Diese Aktion wird ALLE AUFGABEN löschen!', 'Settings' => 'Einstellungen', 'Application settings' => 'Applikationseinstellungen', 'Language' => 'Sprache', 'Webhooks token:' => 'Webhooks token:', 'More information' => 'Mehr Informationen', - 'Database size:' => 'Datenbank Größe :', + 'Database size:' => 'Datenbankgröße:', 'Download the database' => 'Download der Datenbank', 'Optimize the database' => 'Optimieren der Datenbank', '(VACUUM command)' => '(VACUUM command)', '(Gzip compressed Sqlite file)' => '(Gzip komprimierte sqlite Datei)', - 'User settings' => 'Benutzer Eintellungen', - 'My default project:' => 'Mein Standard Projekt : ', + 'User settings' => 'Benutzereinstellungen', + 'My default project:' => 'Mein Standardprojekt:', 'Close a task' => 'Aufgabe schließen', - 'Do you really want to close this task: "%s"?' => 'Willst Du diese Aufgabe wirklich schließen : « %s » ?', + 'Do you really want to close this task: "%s"?' => 'Willst Du diese Aufgabe wirklich schließen: «%s»?', 'Edit a task' => 'Aufgabe bearbeiten', 'Column' => 'Spalte', 'Color' => 'Farbe', @@ -108,18 +108,18 @@ return array( 'Create another task' => 'Neue Aufgabe erstellen', 'New task' => 'Neue Aufgabe', 'Open a task' => 'Öffne eine Aufgabe', - 'Do you really want to open this task: "%s"?' => 'Willst Du diese Aufgabe wirklich öffnen : « %s » ?', + 'Do you really want to open this task: "%s"?' => 'Willst Du diese Aufgabe wirklich öffnen: «%s»?', 'Back to the board' => 'Zurück zur Tafel', 'Created on %B %e, %G at %k:%M %p' => 'Erstellt am %d.%m.%Y um %H:%M', 'There is nobody assigned' => 'Ein gibt keinen Zuständigen', - 'Column on the board:' => 'Spalten auf diesem Tafel : ', + 'Column on the board:' => 'Spalten auf diesem Tafel:', 'Status is open' => 'Status ist geöffnet', 'Status is closed' => 'Status ist geschlossen', 'close this task' => 'Aufgabe schließen', 'open this task' => 'Aufgabe öffnen', - 'There is no description.' => 'Es gibt keine Erklärung.', + 'There is no description.' => 'Es gibt keine Beschreibung.', 'Add a new task' => 'Eine neue Aufgabe hinzufügen', - 'The username is required' => 'Der Butzutzername wird benötigt', + 'The username is required' => 'Der Benutzername wird benötigt', 'The maximum length is %d characters' => 'Die maximale Länge sind %d Zeichen', 'The minimum length is %d characters' => 'Die minimale Länge sind %d Zeichen', 'The password is required' => 'Das Passwort wird benötigt', @@ -133,12 +133,12 @@ return array( 'The project is required' => 'Das Projekt wird benötigt', 'The color is required' => 'Die Farbe wird benötigt', 'The id is required' => 'Die ID wird benötigt', - 'The project id is required' => 'Die Projekt ID wird benötogt', - 'The project name is required' => 'Der Projekt Name wird benötigt', - 'This project must be unique' => 'Der Projekt Name muss eindeutig sein', + 'The project id is required' => 'Die Projekt ID wird benötigt', + 'The project name is required' => 'Der Projektname wird benötigt', + 'This project must be unique' => 'Der Projektname muss eindeutig sein', 'The title is required' => 'Der Titel wird benötigt', 'The language is required' => 'Die Sprache wird benötigt', - 'There is no active project, the first step is to create a new project.' => 'Es gibt kein aktives Projekt. Der erste Schritt ist ein Projekt erstellen.', + 'There is no active project, the first step is to create a new project.' => 'Es gibt kein aktives Projekt. Der erste Schritt ist ein Projekt zu erstellen.', 'Settings saved successfully.' => 'Die Einstellungen wurden erfolgreich gespeichert.', 'Unable to save your settings.' => 'Speicher der Einstellungen nicht möglich.', 'Database optimization done.' => 'Optimieren der Datenbank abgeschlossen.', @@ -148,7 +148,7 @@ return array( 'Unable to update this project.' => 'Ändern des Projekts nicht möglich.', 'Unable to remove this project.' => 'Löschen des Projekts nicht möglich.', 'Project removed successfully.' => 'Löschen des Projekts erfolgreich.', - 'Project activated successfully.' => 'Aktivieren des Projekts erolgreich.', + 'Project activated successfully.' => 'Aktivieren des Projekts erfolgreich.', 'Unable to activate this project.' => 'Aktivieren des Projekts nicht möglich.', 'Project disabled successfully.' => 'Deaktivieren des Projekts erfolgreich.', 'Unable to disable this project.' => 'Deaktivieren des Projekts nicht möglich.', @@ -171,24 +171,24 @@ return array( 'Backlog' => 'Ideen', 'Work in progress' => 'In Arbeit', 'Done' => 'Erledigt', - 'Application version:' => 'Programmversion :', - 'Completed on %B %e, %G at %k:%M %p' => 'Erledigt am %d.%m.%Y à %H:%M', + 'Application version:' => 'Programmversion:', + 'Completed on %B %e, %G at %k:%M %p' => 'Erledigt am %d.%m.%Y um %H:%M', '%B %e, %G at %k:%M %p' => '%d.%m.%Y um %H:%M', - 'Date created' => 'Erstellung am', + 'Date created' => 'Erstellt am', 'Date completed' => 'Erledigt am', 'Id' => 'ID', 'No task' => 'Keine Aufgabe', 'Completed tasks' => 'Erledigte Aufgaben', 'List of projects' => 'Liste der Projekte', - 'Completed tasks for "%s"' => 'Erledigte Aufaben für « %s »', - '%d closed tasks' => '%d erledigte Augaben', + 'Completed tasks for "%s"' => 'Erledigte Aufgaben für «%s»', + '%d closed tasks' => '%d erledigte Aufgaben', 'no task for this project' => 'Keine Aufgaben in diesem Projekt', 'Public link' => 'Öffentlicher Link', - 'There is no column in your project!' => 'Es gibt keine Spalte in Deinem Projekt!', + 'There is no column in your project!' => 'Es gibt keine Spalte in deinem Projekt!', 'Change assignee' => 'Zuständigkeit ändern', - 'Change assignee for the task "%s"' => 'Zuständigkeit für diese Aufgabe ändern: « %s »', + 'Change assignee for the task "%s"' => 'Zuständigkeit für diese Aufgabe ändern: «%s»', 'Timezone' => 'Zeitzone', - 'Sorry, I didn\'t found this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden !', + 'Sorry, I didn\'t found this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!', 'Page not found' => 'Seite nicht gefunden', 'Story Points' => 'Umfang', 'limit' => 'Limit', @@ -197,17 +197,17 @@ return array( 'Edit project access list' => 'Projekt Zugangsberechtigungen bearbeiten', 'Edit users access' => 'Benutzerzugang bearbeiten', 'Allow this user' => 'Diesen Benutzer authorisieren', - 'Project access list for "%s"' => 'Projekt Zugangsliste für « %s »', - 'Only those users have access to this project:' => 'Nur diese Benutzer haben Zugang zum Projekt :', + 'Project access list for "%s"' => 'Projekt Zugangsliste für «%s»', + 'Only those users have access to this project:' => 'Nur diese Benutzer haben Zugang zum Projekt:', 'Don\'t forget that administrators have access to everything.' => 'Vergiss nicht, dass Administratoren überall Zugang haben.', 'revoke' => 'verbieten', - 'List of authorized users' => 'Liste der authorisieren Benutzer', + 'List of authorized users' => 'Liste der authorisierten Benutzer', 'User' => 'Benutzer', - 'Everybody have access to this project.' => 'Jedem hat Zugang zu diesem Projekt.', + 'Everybody have access to this project.' => 'Jeder hat Zugang zu diesem Projekt.', 'You are not allowed to access to this project.' => 'Du hast keinen Zugang zu diesem Projekt.', 'Comments' => 'Kommentare', 'Post comment' => 'kommentieren', - 'Write your text in Markdown' => 'Schreibe Deinen Text in Markdown', + 'Write your text in Markdown' => 'Schreibe deinen Text in Markdown', 'Leave a comment' => 'Kommentar abgeben', 'Comment is required' => 'Ein Kommentar wird benötigt', 'Leave a description' => 'Beschreibung', @@ -226,8 +226,8 @@ return array( 'Unable to create your automatic action.' => 'Deine automatische Aktion konnte nicht erstellt werden.', 'Remove an action' => 'Aktion löschen', 'Unable to remove this action.' => 'Aktion konnte nicht gelöscht werden', - 'Action removed successfully.' => 'Action wurde erfolgreich gelöscht.', - 'Automatic actions for the project "%s"' => 'Automatische Aktionen für dieses Projekt « %s »', + 'Action removed successfully.' => 'Aktion wurde erfolgreich gelöscht.', + 'Automatic actions for the project "%s"' => 'Automatische Aktionen für das Projekt «%s»', 'Defined actions' => 'Definierte Aktionen', 'Add an action' => 'Aktion hinzufügen', 'Event name' => 'Ereignis', @@ -235,15 +235,15 @@ return array( 'Action parameters' => 'Aktionsparameter', 'Action' => 'Aktion', 'Event' => 'Ereignis', - 'When the selected event occurs execute the corresponding action.' => 'Wenn des gewählte Ereignis eintritt, führe die passende Aktion aus.', + 'When the selected event occurs execute the corresponding action.' => 'Wenn des gewählte Ereignis eintritt, führe die zugehörige Aktion aus.', 'Next step' => 'Nächster Schritt', 'Define action parameters' => 'Aktionsparameter definieren', 'Save this action' => 'Aktion speichern', - 'Do you really want to remove this action: "%s"?' => 'Willst Du diese Aktion wirklich löschen « %s » ?', + 'Do you really want to remove this action: "%s"?' => 'Willst Du diese Aktion wirklich löschen «%s»?', 'Remove an automatic action' => 'Löschen einer automatischen Aktion', - 'Close the task' => 'Aufgabe schießen', + 'Close the task' => 'Aufgabe schließen', 'Assign the task to a specific user' => 'Aufgabe einem Benutzer zuordnen', - 'Assign the task to the person who does the action' => 'Aufgabe dem Benutzer zuordnen der die Aktion aufgeführt hat', + 'Assign the task to the person who does the action' => 'Aufgabe dem Benutzer zuordnen der die Aktion ausgeführt hat', 'Duplicate the task to another project' => 'Aufgabe in ein anderes Projekt kopieren', 'Move a task to another column' => 'Aufgabe in eine andere Spalte verschieben', 'Move a task to another position in the same column' => 'Aufgabe an eine andere Position in der gleichen Spalte verschieben', @@ -268,7 +268,7 @@ return array( 'Do you really want to remove this comment?' => 'Willst Du diesen Kommentar wirklich löschen?', 'Only administrators or the creator of the comment can access to this page.' => 'Nur Administratoren und der Ersteller des Kommentars könne diese Seite verwenden.', 'Details' => 'Details', - 'Current password for the user "%s"' => 'Aktuelles Passwort für den Benutzer « %s »', + 'Current password for the user "%s"' => 'Aktuelles Passwort für den Benutzer «%s»', 'The current password is required' => 'Das aktuelle Passwort wird benötigt', 'Wrong password' => 'Falsches Passwort', 'Reset all tokens' => 'Alle Tokens zurücksetzten', @@ -278,8 +278,8 @@ return array( 'Login date' => 'Anmeldedatum', 'Authentication method' => 'Anmeldemethode', 'IP address' => 'IP Adresse', - 'User agent' => 'Benutzer Agent', - 'Persistent connections' => 'Bestende Verbindungen', + 'User agent' => 'User Agent', + 'Persistent connections' => 'Bestehende Verbindungen', 'No session' => 'Keine Session', 'Expiration date' => 'Ablaufdatum', 'Remember Me' => 'Angemeldet bleiben', @@ -291,23 +291,31 @@ return array( 'Closed' => 'Geschlossen', 'Search' => 'Suchen', 'Nothing found.' => 'Nichts gefunden.', - 'Search in the project "%s"' => 'Suche im Projekt « %s »', + 'Search in the project "%s"' => 'Suche im Projekt «%s»', 'Due date' => 'Fälligkeitsdatum', 'Others formats accepted: %s and %s' => 'Andere akzepierte Formate : %s und %s', 'Description' => 'Beschreibung', '%d comments' => '%d Kommentare', '%d comment' => '%d Kommentar', 'Email address invalid' => 'EMail Adresse ungültig', - 'Your Google Account is not linked anymore to your profile.' => 'Dein Google Accout ist nicht mit den Profil verbunden.', - 'Unable to unlink your Google Account.' => 'Trennung der Verbindung mit dem Google Accout nicht möglich.', + 'Your Google Account is not linked anymore to your profile.' => 'Dein Google Account ist nicht mehr mit deinem Profil verbunden.', + 'Unable to unlink your Google Account.' => 'Trennung der Verbindung mit deinem Google Account nicht möglich.', 'Google authentication failed' => 'Zugang mit Google nicht möglich', - 'Unable to link your Google Account.' => 'Verbindung mit dem Google Accout nicht möglich.', - 'Your Google Account is linked to your profile successfully.' => 'Der Google Account wurde erfolgreich verbunden.', + 'Unable to link your Google Account.' => 'Verbindung mit deinem Google Account nicht möglich.', + 'Your Google Account is linked to your profile successfully.' => 'Dein Google Account wurde erfolgreich verbunden.', 'Email' => 'Email', 'Link my Google Account' => 'Verbinde meinen Google Account', 'Unlink my Google Account' => 'Verbindung mit meinem Google Account trennen', 'Login with my Google Account' => 'Anmelden mit meinem Google Account', 'Project not found.' => 'Das Projekt wurde nicht gefunden.', + 'Your GitHub account was successfully linked to your profile.' => 'Dein GitHub Account wurde erfolgreich mit deinem Profil verbunden.', + 'Unable to link your GitHub Account.' => 'Verbindung mit deinem GitHub Account nicht möglich.', + 'GitHub authentication failed' => 'Zugang mit GitHub nicht möglich', + 'Your GitHub account is no longer linked to your profile.' => 'Dein GitHub Account ist nicht mehr mit deinem Profil verbunden.', + 'Unable to unlink your GitHub Account.' => 'Trennung der Verbindung mit deinem GitHub Account nicht möglich.', + 'Login with my GitHub Account' => 'Anmelden mit meinem GitHub Account', + 'Link my GitHub Account' => 'Mit meinem GitHub Account verbinden', + 'Unlink my GitHub Account' => 'Verbindung mit meinem GitHub Account trennen', // 'Task #%d' => '', // 'Task removed successfully.' => '', // 'Unable to remove this task.' => '', diff --git a/app/Locales/es_ES/translations.php b/app/Locales/es_ES/translations.php index 70bb9ffa..98ab30ca 100644 --- a/app/Locales/es_ES/translations.php +++ b/app/Locales/es_ES/translations.php @@ -376,4 +376,12 @@ return array( 'Unable to upload the file.' => 'No pude cargar el fichero.', 'Actions' => 'Acciones', // 'Display another project' => '', + // 'Your GitHub account was successfully linked to your profile.' => '', + // 'Unable to link your GitHub Account.' => '', + // 'GitHub authentication failed' => '', + // 'Your GitHub account is no longer linked to your profile.' => '', + // 'Unable to unlink your GitHub Account.' => '', + // 'Login with my GitHub Account' => '', + // 'Link my GitHub Account' => '', + // 'Unlink my GitHub Account' => '', ); diff --git a/app/Locales/fr_FR/translations.php b/app/Locales/fr_FR/translations.php index 37189680..4405a9a3 100644 --- a/app/Locales/fr_FR/translations.php +++ b/app/Locales/fr_FR/translations.php @@ -374,4 +374,12 @@ return array( 'Maximum size: ' => 'Taille maximum : ', 'Unable to upload the file.' => 'Impossible de transférer le fichier.', 'Display another project' => 'Afficher un autre projet', + 'Your GitHub account was successfully linked to your profile.' => 'Votre compte Github est désormais lié avec votre profile.', + 'Unable to link your GitHub Account.' => 'Impossible de lier votre compte Github.', + 'GitHub authentication failed' => 'L\'authentification avec Github à échouée', + 'Your GitHub account is no longer linked to your profile.' => 'Votre compte Github n\'est plus relié avec votre profile.', + 'Unable to unlink your GitHub Account.' => 'Impossible de déconnecter votre compte Github.', + 'Login with my GitHub Account' => 'Se connecter avec mon compte Github', + 'Link my GitHub Account' => 'Lier mon compte Github', + 'Unlink my GitHub Account' => 'Ne plus utiliser mon compte Github', ); diff --git a/app/Locales/pl_PL/translations.php b/app/Locales/pl_PL/translations.php index 1253be23..7e552d6a 100644 --- a/app/Locales/pl_PL/translations.php +++ b/app/Locales/pl_PL/translations.php @@ -377,4 +377,12 @@ return array( // 'Maximum size: ' => '', // 'Unable to upload the file.' => '', // 'Display another project' => '', + // 'Your GitHub account was successfully linked to your profile.' => '', + // 'Unable to link your GitHub Account.' => '', + // 'GitHub authentication failed' => '', + // 'Your GitHub account is no longer linked to your profile.' => '', + // 'Unable to unlink your GitHub Account.' => '', + // 'Login with my GitHub Account' => '', + // 'Link my GitHub Account' => '', + // 'Unlink my GitHub Account' => '', ); diff --git a/app/Locales/pt_BR/translations.php b/app/Locales/pt_BR/translations.php index 19fd412d..9a571bad 100644 --- a/app/Locales/pt_BR/translations.php +++ b/app/Locales/pt_BR/translations.php @@ -374,4 +374,12 @@ return array( // 'Maximum size: ' => '', // 'Unable to upload the file.' => '', // 'Display another project' => '', + // 'Your GitHub account was successfully linked to your profile.' => '', + // 'Unable to link your GitHub Account.' => '', + // 'GitHub authentication failed' => '', + // 'Your GitHub account is no longer linked to your profile.' => '', + // 'Unable to unlink your GitHub Account.' => '', + // 'Login with my GitHub Account' => '', + // 'Link my GitHub Account' => '', + // 'Unlink my GitHub Account' => '', ); diff --git a/app/Locales/sv_SE/translations.php b/app/Locales/sv_SE/translations.php index 79be2132..b59217a1 100644 --- a/app/Locales/sv_SE/translations.php +++ b/app/Locales/sv_SE/translations.php @@ -376,4 +376,12 @@ return array( 'Maximum size: ' => 'Maxstorlek: ', 'Unable to upload the file.' => 'Kunde inte ladda upp filen.', // 'Display another project' => '', + // 'Your GitHub account was successfully linked to your profile.' => '', + // 'Unable to link your GitHub Account.' => '', + // 'GitHub authentication failed' => '', + // 'Your GitHub account is no longer linked to your profile.' => '', + // 'Unable to unlink your GitHub Account.' => '', + // 'Login with my GitHub Account' => '', + // 'Link my GitHub Account' => '', + // 'Unlink my GitHub Account' => '', ); diff --git a/app/Locales/zh_CN/translations.php b/app/Locales/zh_CN/translations.php index 2144fcef..86628756 100644 --- a/app/Locales/zh_CN/translations.php +++ b/app/Locales/zh_CN/translations.php @@ -382,4 +382,12 @@ return array( // 'Maximum size: ' => '', // 'Unable to upload the file.' => '', // 'Display another project' => '', + // 'Your GitHub account was successfully linked to your profile.' => '', + // 'Unable to link your GitHub Account.' => '', + // 'GitHub authentication failed' => '', + // 'Your GitHub account is no longer linked to your profile.' => '', + // 'Unable to unlink your GitHub Account.' => '', + // 'Login with my GitHub Account' => '', + // 'Link my GitHub Account' => '', + // 'Unlink my GitHub Account' => '', ); diff --git a/app/Model/Acl.php b/app/Model/Acl.php index 035fd7c3..8a87a6b2 100644 --- a/app/Model/Acl.php +++ b/app/Model/Acl.php @@ -17,7 +17,7 @@ class Acl extends Base * @var array */ private $public_actions = array( - 'user' => array('login', 'check', 'google'), + 'user' => array('login', 'check', 'google', 'github'), 'task' => array('add'), 'board' => array('readonly'), ); @@ -32,7 +32,7 @@ class Acl extends Base 'app' => array('index'), 'board' => array('index', 'show', 'assign', 'assigntask', 'save', 'check'), 'project' => array('tasks', 'index', 'forbidden', 'search'), - 'user' => array('index', 'edit', 'update', 'forbidden', 'logout', 'index', 'unlinkgoogle'), + 'user' => array('index', 'edit', 'update', 'forbidden', 'logout', 'index', 'unlinkgoogle', 'unlinkgithub'), 'config' => array('index', 'removeremembermetoken'), 'comment' => array('create', 'save', 'confirm', 'remove', 'update', 'edit', 'forbidden'), 'file' => array('create', 'save', 'download', 'confirm', 'remove', 'open', 'image'), diff --git a/app/Model/GitHub.php b/app/Model/GitHub.php new file mode 100644 index 00000000..3380218d --- /dev/null +++ b/app/Model/GitHub.php @@ -0,0 +1,178 @@ +<?php
+
+namespace Model;
+
+require __DIR__.'/../../vendor/OAuth/bootstrap.php';
+
+use OAuth\Common\Storage\Session;
+use OAuth\Common\Consumer\Credentials;
+use OAuth\Common\Http\Uri\UriFactory;
+use OAuth\ServiceFactory;
+use OAuth\Common\Http\Exception\TokenResponseException;
+
+/**
+ * GitHub model
+ *
+ * @package model
+ */
+class GitHub extends Base
+{
+ /**
+ * Authenticate a GitHub user
+ *
+ * @access public
+ * @param string $github_id GitHub user id
+ * @return boolean
+ */
+ public function authenticate($github_id)
+ {
+ $userModel = new User($this->db, $this->event);
+
+ $user = $userModel->getByGitHubId($github_id);
+
+ if ($user) {
+
+ // Create the user session
+ $userModel->updateSession($user);
+
+ // Update login history
+ $lastLogin = new LastLogin($this->db, $this->event);
+ $lastLogin->create(
+ LastLogin::AUTH_GITHUB,
+ $user['id'],
+ $userModel->getIpAddress(),
+ $userModel->getUserAgent()
+ );
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Unlink a GitHub account for a given user
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @return boolean
+ */
+ public function unlink($user_id)
+ {
+ $userModel = new User($this->db, $this->event);
+
+ return $userModel->update(array(
+ 'id' => $user_id,
+ 'github_id' => '',
+ ));
+ }
+
+ /**
+ * Update the user table based on the GitHub profile information
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @param array $profile GitHub profile
+ * @return boolean
+ * @todo Don't overwrite existing email/name with empty GitHub data
+ */
+ public function updateUser($user_id, array $profile)
+ {
+ $userModel = new User($this->db, $this->event);
+
+ return $userModel->update(array(
+ 'id' => $user_id,
+ 'github_id' => $profile['id'],
+ 'email' => $profile['email'],
+ 'name' => $profile['name'],
+ ));
+ }
+
+ /**
+ * Get the GitHub service instance
+ *
+ * @access public
+ * @return \OAuth\OAuth2\Service\GitHub
+ */
+ public function getService()
+ {
+ $uriFactory = new UriFactory();
+ $currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER);
+ $currentUri->setQuery('controller=user&action=gitHub');
+
+ $storage = new Session(false);
+
+ $credentials = new Credentials(
+ GITHUB_CLIENT_ID,
+ GITHUB_CLIENT_SECRET,
+ $currentUri->getAbsoluteUri()
+ );
+
+ $serviceFactory = new ServiceFactory();
+
+ return $serviceFactory->createService(
+ 'gitHub',
+ $credentials,
+ $storage,
+ array('')
+ );
+ }
+
+ /**
+ * Get the authorization URL
+ *
+ * @access public
+ * @return \OAuth\Common\Http\Uri\Uri
+ */
+ public function getAuthorizationUrl()
+ {
+ return $this->getService()->getAuthorizationUri();
+ }
+
+ /**
+ * Get GitHub profile information from the API
+ *
+ * @access public
+ * @param string $code GitHub authorization code
+ * @return bool|array
+ */
+ public function getGitHubProfile($code)
+ {
+ try {
+ $gitHubService = $this->getService();
+ $gitHubService->requestAccessToken($code);
+
+ return json_decode($gitHubService->request('user'), true);
+ }
+ catch (TokenResponseException $e) {
+ return false;
+ }
+
+ return false;
+ }
+
+ /**
+ * Revokes this user's GitHub tokens for Kanboard
+ *
+ * @access public
+ * @return bool|array
+ * @todo Currently this simply removes all our tokens for this user, ideally it should
+ * restrict itself to the one in question
+ */
+ public function revokeGitHubAccess()
+ {
+ try {
+ $gitHubService = $this->getService();
+
+ $basicAuthHeader = array('Authorization' => 'Basic ' .
+ base64_encode(GITHUB_CLIENT_ID.':'.GITHUB_CLIENT_SECRET));
+
+ return json_decode($gitHubService->request('/applications/'.GITHUB_CLIENT_ID.'/tokens', 'DELETE', null, $basicAuthHeader), true);
+ }
+ catch (TokenResponseException $e) {
+ return false;
+ }
+
+ return false;
+ }
+}
diff --git a/app/Model/LastLogin.php b/app/Model/LastLogin.php index 56739b48..db4c4a57 100644 --- a/app/Model/LastLogin.php +++ b/app/Model/LastLogin.php @@ -33,6 +33,7 @@ class LastLogin extends Base const AUTH_REMEMBER_ME = 'remember_me'; const AUTH_LDAP = 'ldap'; const AUTH_GOOGLE = 'google'; + const AUTH_GITHUB = 'github'; /** * Create a new record diff --git a/app/Model/User.php b/app/Model/User.php index 8769d69a..ba1acb90 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -52,6 +52,18 @@ class User extends Base } /** + * Get a specific user by the GitHub id + * + * @access public + * @param string $github_id GitHub user id + * @return array + */ + public function getByGitHubId($github_id) + { + return $this->db->table(self::TABLE)->eq('github_id', $github_id)->findOne(); + } + + /** * Get a specific user by the username * * @access public diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 7ff130cf..a30b0989 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -3,7 +3,12 @@ namespace Schema; use Core\Security; -const VERSION = 19; +const VERSION = 20; + +function version_20($pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN github_id VARCHAR(30)"); +} function version_19($pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index 8f8e498a..6ea0541c 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -3,7 +3,12 @@ namespace Schema; use Core\Security; -const VERSION = 19; +const VERSION = 20; + +function version_20($pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN github_id TEXT"); +} function version_19($pdo) { diff --git a/app/Templates/board_task.php b/app/Templates/board_task.php index fa745ac1..89f768e0 100644 --- a/app/Templates/board_task.php +++ b/app/Templates/board_task.php @@ -20,13 +20,13 @@ <?php else: ?> - <a href="?controller=task&action=edit&task_id=<?= $task['id'] ?>" title="<?= t('Edit this task') ?>">#<?= $task['id'] ?></a> - + <a class="task-board-popover" href="?controller=task&action=edit&task_id=<?= $task['id'] ?>" title="<?= t('Edit this task') ?>">#<?= $task['id'] ?></a> - <span class="task-board-user"> <?php if (! empty($task['owner_id'])): ?> - <a href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>"><?= t('Assigned to %s', $task['username']) ?></a> + <a class="task-board-popover" href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>"><?= t('Assigned to %s', $task['username']) ?></a> <?php else: ?> - <a href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>" class="task-board-nobody"><?= t('Nobody assigned') ?></a> + <a class="task-board-popover" href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>" class="task-board-nobody"><?= t('Nobody assigned') ?></a> <?php endif ?> </span> @@ -69,8 +69,8 @@ <?php endif ?> <?php if (! empty($task['description'])): ?> - <i class="fa fa-file-text-o" title="<?= t('Description') ?>"></i> + <a class="task-board-popover" href='?controller=task&action=editDescription&task_id=<?= $task['id'] ?>'><i class="fa fa-file-text-o" title="<?= t('Description') ?>"></i></a> <?php endif ?> </div> </div> -<?php endif ?>
\ No newline at end of file +<?php endif ?> diff --git a/app/Templates/task_edit.php b/app/Templates/task_edit.php index 015f746d..07c3539b 100644 --- a/app/Templates/task_edit.php +++ b/app/Templates/task_edit.php @@ -1,12 +1,14 @@ <section id="main"> <div class="page-header"> <h2><?= t('Edit a task') ?></h2> +<?php if (!$ajax): ?> <ul> <li><a href="?controller=board&action=show&project_id=<?= $task['project_id'] ?>"><?= t('Back to the board') ?></a></li> </ul> +<?php endif ?> </div> <section> - <form method="post" action="?controller=task&action=update&task_id=<?= $task['id'] ?>" autocomplete="off"> + <form method="post" action="?controller=task&action=update&task_id=<?= $task['id'] ?>&ajax=<?= $ajax ?>" autocomplete="off"> <?= Helper\form_csrf() ?> @@ -47,7 +49,12 @@ <div class="form-actions"> <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> - <?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a> + <?= t('or') ?> +<?php if ($ajax): ?> + <a href="?controller=board&action=show&project_id=<?= $task['project_id'] ?>"><?= t('cancel') ?></a> +<?php else: ?> + <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a> +<?php endif ?> </div> </form> </section> diff --git a/app/Templates/task_edit_description.php b/app/Templates/task_edit_description.php index 550dac73..ba0d3887 100644 --- a/app/Templates/task_edit_description.php +++ b/app/Templates/task_edit_description.php @@ -2,7 +2,7 @@ <h2><?= t('Edit the description') ?></h2> </div> -<form method="post" action="?controller=task&action=saveDescription&task_id=<?= $task['id'] ?>" autocomplete="off"> +<form method="post" action="?controller=task&action=saveDescription&task_id=<?= $task['id'] ?>&ajax=<?= $ajax ?>" autocomplete="off"> <?= Helper\form_csrf() ?> @@ -13,6 +13,11 @@ <div class="form-actions"> <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> <?= t('or') ?> +<?php if ($ajax): ?> + <a href="?controller=board&action=show&project_id=<?= $task['project_id'] ?>"><?= t('cancel') ?></a> +<?php else: ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a> +<?php endif ?> </div> </form> + diff --git a/app/Templates/user_edit.php b/app/Templates/user_edit.php index 6b83f748..8fba922f 100644 --- a/app/Templates/user_edit.php +++ b/app/Templates/user_edit.php @@ -48,14 +48,27 @@ <?= Helper\form_checkbox('is_admin', t('Administrator'), 1, isset($values['is_admin']) && $values['is_admin'] == 1 ? true : false) ?><br/> <?php endif ?> + <ul> <?php if (GOOGLE_AUTH && Helper\is_current_user($values['id'])): ?> + <li> <?php if (empty($values['google_id'])): ?> <a href="?controller=user&action=google<?= Helper\param_csrf() ?>"><?= t('Link my Google Account') ?></a> <?php else: ?> <a href="?controller=user&action=unlinkGoogle<?= Helper\param_csrf() ?>"><?= t('Unlink my Google Account') ?></a> <?php endif ?> + </li> <?php endif ?> + <?php if (GITHUB_AUTH && Helper\is_current_user($values['id'])): ?> + <li> + <?php if (empty($values['github_id'])): ?> + <a href="?controller=user&action=gitHub<?= Helper\param_csrf() ?>"><?= t('Link my GitHub Account') ?></a> + <?php else: ?> + <a href="?controller=user&action=unlinkGitHub<?= Helper\param_csrf() ?>"><?= t('Unlink my GitHub Account') ?></a> + <?php endif ?> + </li> + <?php endif ?> + </ul> </div> <div class="form-actions"> diff --git a/app/Templates/user_login.php b/app/Templates/user_login.php index 49902ebb..948a19e7 100644 --- a/app/Templates/user_login.php +++ b/app/Templates/user_login.php @@ -18,12 +18,20 @@ <?= Helper\form_checkbox('remember_me', t('Remember Me'), 1) ?><br/> + <ul> <?php if (GOOGLE_AUTH): ?> - <p> + <li> <a href="?controller=user&action=google"><?= t('Login with my Google Account') ?></a> - </p> + </li> <?php endif ?> + <?php if (GITHUB_AUTH): ?> + <li> + <a href="?controller=user&action=gitHub"><?= t('Login with my GitHub Account') ?></a> + </li> + <?php endif ?> + </ul> + <div class="form-actions"> <input type="submit" value="<?= t('Sign in') ?>" class="btn btn-blue"/> </div> diff --git a/app/common.php b/app/common.php index c5fb34e2..d607cf92 100644 --- a/app/common.php +++ b/app/common.php @@ -53,6 +53,11 @@ defined('GOOGLE_AUTH') or define('GOOGLE_AUTH', false); defined('GOOGLE_CLIENT_ID') or define('GOOGLE_CLIENT_ID', ''); defined('GOOGLE_CLIENT_SECRET') or define('GOOGLE_CLIENT_SECRET', ''); +// GitHub authentication +defined('GITHUB_AUTH') or define('GITHUB_AUTH', false); +defined('GITHUB_CLIENT_ID') or define('GITHUB_CLIENT_ID', ''); +defined('GITHUB_CLIENT_SECRET') or define('GITHUB_CLIENT_SECRET', ''); + $loader = new Loader; $loader->execute(); diff --git a/assets/css/app.css b/assets/css/app.css index 60d37abc..b35ce05e 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -249,11 +249,21 @@ input.form-date { padding-left: 0; } +.form-column ul { + margin-top: 15px; +} + .form-login { padding-left: 20px; width: 430px; } +.form-column li, +.form-login li { + margin-left: 25px; + line-height: 25px; +} + /* alerts */ .alert { padding: 8px 35px 8px 14px; diff --git a/assets/js/board.js b/assets/js/board.js index f43f3f57..0487005d 100644 --- a/assets/js/board.js +++ b/assets/js/board.js @@ -15,14 +15,14 @@ }); // Open assignee popover - $(".task-board-user a").click(function(e) { + $(".task-board-popover").click(function(e) { e.preventDefault(); e.stopPropagation(); - var taskId = $(this).parent().parent().attr("data-task-id"); + var href = $(this).attr('href'); - $.get("?controller=board&action=assign&task_id=" + taskId, function(data) { + $.get(href, function(data) { popover_show(data); }); }); diff --git a/config.default.php b/config.default.php index e3551994..027d8417 100644 --- a/config.default.php +++ b/config.default.php @@ -62,3 +62,12 @@ define('GOOGLE_CLIENT_ID', ''); // Google client secret key (Get this value from the Google developer console) define('GOOGLE_CLIENT_SECRET', ''); + +// Enable/disable GitHub authentication +define('GITHUB_AUTH', false); + +// GitHub client id (Copy it from your settings -> Applications -> Developer applications) +define('GITHUB_CLIENT_ID', ''); + +// GitHub client secret key (Copy it from your settings -> Applications -> Developer applications) +define('GITHUB_CLIENT_SECRET', ''); diff --git a/docs/github-authentication.markdown b/docs/github-authentication.markdown new file mode 100644 index 00000000..ef99bb80 --- /dev/null +++ b/docs/github-authentication.markdown @@ -0,0 +1,62 @@ +GitHub Authentication
+=====================
+
+Requirements
+------------
+
+- OAuth GitHub API credentials (available in your [Settings > Applications > Developer applications](https://github.com/settings/applications))
+
+How does this work?
+-------------------
+
+The GitHub authentication in Kanboard uses the [OAuth 2.0](http://oauth.net/2/) protocol, so any user of Kanboard can be linked to a GitHub account. When that is done, they no longer need to manually login with their Kanboard account, but can simply automatically login with their GitHub account.
+
+How to link a GitHub account
+----------------------------------
+
+1. Login to Kanboard with the desired user
+2. Go to the **Edit user** page and click on the link **Link my GitHub Account**
+3. You are redirected to the GitHub **Authorize application** form, authorize Kanboard by clicking on the button **Accept**
+4. Finally, you are redirected to Kanboard and now your user account is linked to your GitHub account
+5. During the process, Kanboard has updated your full name and your email address based on your GitHub profile, if either of those are publically available
+6. Log out of Kanboard and you should be able to login directly with GitHub by clicking on the link **Login with my GitHub Account**
+
+Installation instructions
+-------------------------
+
+### Setting up OAuth 2.0
+
+If you know what you're doing, you can directly go to the ["Register a new OAuth application"](https://github.com/settings/applications/new) site, set everything up and skip to [Setting up Kanboard](#setting-up-kanboard) below.
+
+Summarizing the [official GitHub documentation](https://developer.github.com/guides/basics-of-authentication/#registering-your-app):
+
+- Open your [**Settings**](https://github.com/settings), select [**Applications**](https://github.com/settings/applications) from the sidebar and click on [**Register new application**](https://github.com/settings/applications/new) on the top, next to where it says **Developer applications**
+- Fill out the form with whatever values you like, only the **Authorization callback URL** _must_ be: **http://YOUR_SERVER/?controller=user&action=gitHub**
+
+### Setting up Kanboard
+
+Either create a new `config.php` file or copy and rename the `config.default.php` file and set the following values:
+
+```php
+// Enable/disable GitHub authentication
+define('GITHUB_AUTH', true);
+
+// GitHub client id (Copy it from your settings -> Applications -> Developer applications)
+define('GITHUB_CLIENT_ID', 'YOUR_GITHUB_CLIENT_ID');
+
+// GitHub client secret key (Copy it from your settings -> Applications -> Developer applications)
+define('GITHUB_CLIENT_SECRET', 'YOUR_GITHUB_CLIENT_SECRET');
+
+```
+
+Notes
+-----
+**Important:** _*Never*_ store your GITHUB_CLIENT_ID or GITHUB_CLIENT_SECRET in GitHub or somewhere with full public access in general!
+
+Kanboard uses these information from your public GitHub profile:
+
+- Full name
+- Public email address
+- GitHub unique id
+
+The GitHub unique id is used to link the local user account and the GitHub account.
diff --git a/jsonrpc.php b/jsonrpc.php index 981fefa2..186e454d 100644 --- a/jsonrpc.php +++ b/jsonrpc.php @@ -10,7 +10,7 @@ use Model\User; use Model\Config; use Model\Category; use Model\Comment; -use Model\Subtask; +use Model\SubTask; use Model\Board; use Model\Action; @@ -20,7 +20,7 @@ $task = new Task($registry->shared('db'), $registry->shared('event')); $user = new User($registry->shared('db'), $registry->shared('event')); $category = new Category($registry->shared('db'), $registry->shared('event')); $comment = new Comment($registry->shared('db'), $registry->shared('event')); -$subtask = new Subtask($registry->shared('db'), $registry->shared('event')); +$subtask = new SubTask($registry->shared('db'), $registry->shared('event')); $board = new Board($registry->shared('db'), $registry->shared('event')); $action = new Action($registry->shared('db'), $registry->shared('event')); @@ -123,6 +123,14 @@ $server->register('updateTask', function($values) use ($task) { return $valid && $task->update($values); }); +$server->register('openTask', function($task_id) use ($task) { + return $task->open($task_id); +}); + +$server->register('closeTask', function($task_id) use ($task) { + return $task->close($task_id); +}); + $server->register('removeTask', function($task_id) use ($task) { return $task->remove($task_id); }); diff --git a/scripts/make-archive.sh b/scripts/make-archive.sh index 0c1d8fee..7edb90c7 100755 --- a/scripts/make-archive.sh +++ b/scripts/make-archive.sh @@ -5,7 +5,7 @@ APP="kanboard" cd /tmp rm -rf /tmp/$APP /tmp/$APP-*.zip 2>/dev/null -git clone git@github.com:fguillot/$APP.git +git clone https://github.com/fguillot/$APP.git rm -rf $APP/data/*.sqlite $APP/.git $APP/.gitignore $APP/scripts $APP/tests $APP/Vagrantfile $APP/.*.yml $APP/phpunit.xml $APP/README.markdown $APP/docs sed -i.bak s/master/$VERSION/g $APP/app/common.php && rm -f $APP/app/*.bak zip -r $APP-$VERSION.zip $APP |