Merge pull-request: Github authentication #162

This commit is contained in:
Frédéric Guillot 2014-06-30 21:52:02 -03:00
parent f70ac7d65f
commit 06d0b7048e
23 changed files with 488 additions and 57 deletions

View File

@ -1,6 +1,7 @@
language: php
php:
- "5.6"
- "5.5"
- "5.4"
- "5.3"

View File

@ -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
@ -104,6 +104,7 @@ Documentation
- [LDAP authentication](docs/ldap-authentication.markdown)
- [Google authentication](docs/google-authentication.markdown)
- [GitHub authentication](docs/github-authentication.markdown)
#### Developers

View File

@ -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

View File

@ -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');
}
}

View File

@ -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.' => '',

View File

@ -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' => '',
);

View File

@ -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',
);

View File

@ -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' => '',
);

View File

@ -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' => '',
);

View File

@ -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' => '',
);

View File

@ -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' => '',
);

View File

@ -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'),

178
app/Model/GitHub.php Normal file
View File

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

View File

@ -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

View File

@ -51,6 +51,18 @@ class User extends Base
return $this->db->table(self::TABLE)->eq('google_id', $google_id)->findOne();
}
/**
* 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
*

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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&amp;action=google<?= Helper\param_csrf() ?>"><?= t('Link my Google Account') ?></a>
<?php else: ?>
<a href="?controller=user&amp;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&amp;action=gitHub<?= Helper\param_csrf() ?>"><?= t('Link my GitHub Account') ?></a>
<?php else: ?>
<a href="?controller=user&amp;action=unlinkGitHub<?= Helper\param_csrf() ?>"><?= t('Unlink my GitHub Account') ?></a>
<?php endif ?>
</li>
<?php endif ?>
</ul>
</div>
<div class="form-actions">

View File

@ -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&amp;action=google"><?= t('Login with my Google Account') ?></a>
</p>
</li>
<?php endif ?>
<?php if (GITHUB_AUTH): ?>
<li>
<a href="?controller=user&amp;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>

View File

@ -51,6 +51,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();

View File

@ -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;

View File

@ -43,3 +43,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', '');

View File

@ -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.