Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] GitHub integration #7

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
26 changes: 25 additions & 1 deletion include/functions.inc
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ function head($title="", $config = []) {
["href" => "/manage/event.php", "text" => "Events"],
["href" => "/manage/users.php", "text" => "Users"],
["href" => "/manage/user-notes.php", "text" => "Notes"],
["href" => "/manage/github.php", "text" => "Github"],
];
$CSS = ["/styles/master.css"];
$SEARCH = [];
Expand Down Expand Up @@ -258,6 +257,11 @@ function find_group_address_from_notes_for($id) {
define("MT_USER_APPROVE_MAIL", "[email protected]");
define("MT_USER_REMOVE_MAIL", "[email protected]");
function user_approve($id) {
if (!is_admin($_SESSION["username"])) {
warn("you're not allowed to take actions on users.");
exit;
}

$res = db_query_safe("UPDATE users SET cvsaccess=1, enable=1 WHERE userid=?", [$id]);
if ($res && mysql_affected_rows()) {
$cc = find_group_address_from_notes_for($id);
Expand All @@ -284,6 +288,10 @@ function user_approve($id) {
}

function user_remove($id) {
if (!is_admin($_SESSION["username"])) {
warn("you're not allowed to take actions on users.");
exit;
}
$userinfo = fetch_user($id);
$res = db_query_safe("DELETE FROM users WHERE userid=?", [$id]);
if ($res && mysql_affected_rows()) {
Expand Down Expand Up @@ -313,6 +321,21 @@ function user_remove($id) {
}
}

function user_unlink_github($id) {
$db = DB::connect();

if(!can_modify($_SESSION['username'], $id)) {
warn("you're not allowed to take actions on users.");
exit;
}

$query = $db->prepare('UPDATE users SET github = ? WHERE userid = ?');
$query->execute([null, (int)$id]);

warn("record $id updated");
exit;
}

function is_admin($user) {
$admins = [
"jimw",
Expand Down Expand Up @@ -428,6 +451,7 @@ function validateAction($k) {
switch($k) {
case "approve":
case "remove":
case "github_unlink":
return $k;
default:
warn("that action ('" . hsc($k) . "') is not understood.");
Expand Down
199 changes: 44 additions & 155 deletions public/manage/github.php
Original file line number Diff line number Diff line change
@@ -1,176 +1,65 @@
<?php // vim: et ts=2 sw=2
<?php

// This script evolved from a quick'n'dirty shell script. If you are reading
// this feel free to clean it!
use App\GitHub\Client;
use App\GitHub\OAuthClient;

require __DIR__ . '/../../vendor/autoload.php';
require __DIR__ . '/../../include/login.inc';
require __DIR__ . '/../../github-config.php';

@include __DIR__ . '/../../github-config.php';
if (!defined('GITHUB_CLIENT_ID') || !defined('GITHUB_CLIENT_SECRET')) {
die('GITHUB_CLIENT_ID or GITHUB_CLIENT_SECRET not defined. Please verify ./github-config.php');
}

define('GITHUB_PHP_OWNER_TEAM_ID', 65141);
define('GITHUB_REPO_TEAM_ID', 138591);
if (!defined('GITHUB_USER_AGENT')) {
define('GITHUB_USER_AGENT', 'php.net repository management (main.php.net, [email protected], [email protected])');
head("github administration");
warn('GITHUB_CLIENT_ID or GITHUB_CLIENT_SECRET not defined. Please verify ./github-config.php');
foot();
exit;
}

function github_api($endpoint, $method = 'GET', $options = [])
{
$options['method'] = $method;
$options['user_agent'] = GITHUB_USER_AGENT;

$ctxt = stream_context_create(['http' => $options]);

$url = 'https://api.github.com'.$endpoint;
$s = @file_get_contents($url, false, $ctxt);
if ($s === false) {
die('Request to GitHub failed. Endpoint: '.$endpoint);
}

return json_decode($s);
$oauth = new OAuthClient(GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET);
if (!isset($_GET['code'])) {
header('Location: ' . $oauth->getRequestCodeUrl());
exit;
}

function github_current_user($access_token = false)
{
if (!$access_token) {
$access_token = $_SESSION['github']['access_token'];
}
head("github administration");

if (empty($_SESSION['github']['current_user'])) {
$user = github_api('/user?access_token='.urlencode($access_token));
if (!$user->login) {
die('Failed to get current user');
}
try {
if (isset($_GET['code'])) {
$response = $oauth->requestAccessToken($_GET['code']);
if (!isset($response['access_token'])) {
throw new RuntimeException('Can not receive the access token');
}

$_SESSION['github']['current_user'] = $user;
}
$client = new Client($response['access_token']);
$user = $client->me();

return $_SESSION['github']['current_user'];
}
if (!isset($user['login'])) {
throw new RuntimeException('Can not get the user GitHub login');
}

function github_require_valid_user()
{
if (isset($_SESSION['github']['access_token'])) {
return true;
}
$username = $_SESSION['credentials'][0];

if (isset($_GET['code'])) {
$data = [
'client_id' => GITHUB_CLIENT_ID,
'client_secret' => GITHUB_CLIENT_SECRET,
'code' => $_GET['code']
];
$data_encoded = http_build_query($data);
$opts = [
'method' => 'POST',
'user_agent' => GITHUB_USER_AGENT,
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $data_encoded,
];
$ctxt = stream_context_create(['http' => $opts]);
$s = @file_get_contents('https://github.com/login/oauth/access_token', false, $ctxt);
if (!$s) {
die('Failed while checking with GitHub,either you are trying to hack us or our configuration is wrong (GITHUB_CLIENT_SECRET outdated?)');
}
$gh = [];
parse_str($s, $gh);
if (empty($gh['access_token'])) {
die("GitHub responded but didn't send an access_token");
}
$db = \App\DB::connect();
$query = $db->prepare('SELECT userid FROM users WHERE username = ?');
$query->execute([$username]);
if (!$query->rowCount()) {
throw new RuntimeException('was not able to find user matching ' . $username);
}

$user = github_current_user($gh['access_token']);
$account = $query->fetch();
$query = $db->prepare('SELECT userid FROM users WHERE github = ? AND userid != ?');
$query->execute([$user['login'], $account['userid']]);
if ($query->rowCount() > 0) {
throw new RuntimeException('GitHub account ' . $user['login'] . ' is already linked');
}

$endpoint = '/teams/'.urlencode((string)GITHUB_PHP_OWNER_TEAM_ID).'/members/'.urlencode($user->login);
$opts = ['user_agent' => GITHUB_USER_AGENT];
$ctxt = stream_context_create(['http' => $opts]);
$is_member = file_get_contents('https://api.github.com'.$endpoint.'?access_token='.urlencode($gh['access_token']), false, $ctxt);
$query = $db->prepare('UPDATE users SET github = ? WHERE userid = ?');
$query->execute([$user['login'], $account['userid']]);

if ($is_member === false) {
head("github administration");
echo '<h1>You (Authenticated GitHub user: '.htmlentities($user->login). ') are no member of the php organization on github.</h1>'.
'<p>Please contact an existing member if you see need.</p>';
foot();
exit;
echo '<h1>We linked your GitHub account with your profile.</h1>' .
'<p><a href="/manage/users.php?username=' . $username . '">Back to profile</a></p>';
}
// SUCCESS
$_SESSION['github']['access_token'] = $gh['access_token'];
header('Location: github.php');
exit;
}

// Start oauth
header('Location: https://github.com/login/oauth/authorize?scope=repo&client_id='.urlencode(GITHUB_CLIENT_ID));
exit;
}

if (isset($_POST['description']) && isset($_SESSION['github']['access_token'])) {
action_create_repo();
} elseif (isset($_GET['login']) || isset($_GET['code']) || isset($_SESSION['github']['access_token'])) {
action_form();
} else {
action_default();
}

function action_default()
{
head("github administration");
echo '<p>This tool is for administrating PHP repos on GitHub. Currently it is used for adding repos only.</p>';
echo '<p><b>NOTE:</b> Only members of the PHP organisation on GitHub can use this tool. We try to keep the number of members limited.</p>';
echo '<p>In case you are a member you can <a href="github.php?login=1">login using GitHub</a>.</p>';
foot();
}

function action_form()
{
github_require_valid_user();
$user = $_SESSION['github']['current_user'];
head("github administration");
?>
<p><b>GitHub user: </b> <?php echo htmlentities($user->login); ?></p>
<p>Creating a GitHub repo using this form ensures the proper configuration. This
includes disabling the GitHub wiki and issue tracker as well as enabling the
php-pulls user to push changes made on git.php.net.</p>
<p>The name, description and homepage should follow other existing repositories.</p>
<form method="post" action="github.php">
Github repo name: https://github.com/php/<input name="name"> (i.e. pecl-category-foobar)<br>
Description: <input name="description"> (i.e. PECL foobar extension)<br>
Homepage: <input name="homepage"> (i.e. http://pecl.php.net/package/foobar)<br>
<input type="submit" value="Create Repository on GitHub">
<input type="hidden" name="action" value="create">
<?php
foot();
} catch (\Exception $e) {
warn($e->getMessage());
}

function action_create_repo()
{
github_require_valid_user();

$data = [
'name' => $_POST['name'],
'description' => $_POST['description'],

'homepage' => $_POST['homepage'],
'private' => false,
'has_issues' => false,
'has_wiki' => false,
'has_downloads' => false,
'team_id' => GITHUB_REPO_TEAM_ID,
];
$data_j = json_encode($data);
$opts = [
'content' => $data_j,
];
$res = github_api('/orgs/php/repos?access_token='.urlencode($_SESSION['github']['access_token']), 'POST', $opts);

head("github administration");
if (isset($res->html_url)) {
echo '<p>Repo created!</p><p><a href="'.htmlentities($res->html_url, ENT_QUOTES).'">Check on GitHub</a>.</p>';
} else {
echo "Error while creating repo.";
}
foot();
}
?>
foot();
37 changes: 28 additions & 9 deletions public/manage/users.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,6 @@ function csrf_validate(&$mydata, $name) {
$action = filter_input(INPUT_POST, "action", FILTER_CALLBACK, ["options" => "validateAction"]);
if ($id && $action) {
csrf_validate($_SESSION, $action);
if (!is_admin($_SESSION["username"])) {
warn("you're not allowed to take actions on users.");
exit;
}

switch ($action) {
case 'approve':
user_approve((int)$id);
Expand All @@ -98,6 +93,10 @@ function csrf_validate(&$mydata, $name) {
user_remove((int)$id);
break;

case 'github_unlink':
user_unlink_github((int)$id);
break;

default:
warn("that action ('$action') is not understood.");
}
Expand Down Expand Up @@ -199,6 +198,24 @@ function csrf_validate(&$mydata, $name) {
<td><?php echo hsc($userdata['username']);?></td>
<?php endif ?>
</tr>
<tr>
<th>GitHub account:</th>
<?php if ($github = $userdata['github']): ?>
<td><?php echo hsc($github);?>
<?php if(can_modify($_SESSION['username'], $id)) {?>
<form method="post" action="users.php?id=<?php echo $id?>">
<input type="hidden" name="csrf" value="<?php echo csrf_generate($_SESSION, 'github_unlink') ?>" />
<input type="hidden" name="action" value="github_unlink" />
<input type="submit" value="Unlink" />
</form>
<?php } ?>
</td>
<?php elseif(can_modify($_SESSION["username"],$id)): ?>
<td><a href="/manage/github.php">Link GitHub account</a></td>
<?php else: ?>
<td>&mdash;</td>
<?php endif ?>
</tr>
<tr>
<td colspan="2">Leave password fields blank to leave password unchanged.</td>
</tr>
Expand Down Expand Up @@ -321,7 +338,7 @@ function csrf_validate(&$mydata, $name) {
$search = filter_input(INPUT_GET, "search", FILTER_UNSAFE_RAW) ?: "";
$order = filter_input(INPUT_GET, "order", FILTER_UNSAFE_RAW) ?: "";

$query = new Query("SELECT DISTINCT SQL_CALC_FOUND_ROWS users.userid,cvsaccess,username,name,email,GROUP_CONCAT(note) note FROM users ");
$query = new Query("SELECT DISTINCT SQL_CALC_FOUND_ROWS users.userid,cvsaccess,username,name,email,github,GROUP_CONCAT(note) note FROM users ");
$query->add(" LEFT JOIN users_note ON users_note.userid = users.userid ");

if ($search) {
Expand All @@ -338,7 +355,7 @@ function csrf_validate(&$mydata, $name) {
$query->add(" GROUP BY users.userid ");

if ($order) {
if (!in_array($order, ["username", "name", "email", "note"], true)) {
if (!in_array($order, ["username", "name", "email", "note", "github"], true)) {
die("Invalid order!");
}
if ($forward) {
Expand Down Expand Up @@ -380,7 +397,8 @@ function csrf_validate(&$mydata, $name) {
<th><a href="?<?php echo array_to_url($extra,["order"=>"username"]);?>">username</a></th>
<th><a href="?<?php echo array_to_url($extra,["order"=>"name"]);?>">name</a></th>
<?php if (!$unapproved) { ?>
<th colspan="2"><a href="?<?php echo array_to_url($extra,["order"=>"email"]);?>">email</a></th>
<th><a href="?<?php echo array_to_url($extra,["order"=>"email"]);?>">email</a></th>
<th><a href="?<?php echo array_to_url($extra,["order"=>"github"]);?>">github</a></th>
<?php } else { ?>
<th><a href="?<?php echo array_to_url($extra,["order"=>"email"]);?>">email</a></th>
<th><a href="?<?php echo array_to_url($extra,["order"=>"note"]);?>">note</a></th>
Expand All @@ -395,7 +413,8 @@ function csrf_validate(&$mydata, $name) {
<td><a href="https://people.php.net/?username=<?php echo hsc($userdata['username']) ?>"><?php echo hsc($userdata['username']) ?></a></td>
<td><?php echo hsc($userdata['name']);?></td>
<?php if (!$unapproved) { ?>
<td colspan="2"><?php echo hsc($userdata['email']);?></td>
<td><?php echo hsc($userdata['email']);?></td>
<td><a href="https://github.com/<?php echo hsc($userdata['github']) ?>"><?php echo hsc($userdata['github']);?></a></td>
<?php } else { ?>
<td><?php echo hsc($userdata['email']);?></td>
<td><?php echo hsc($userdata['note']) ?></td>
Expand Down
1 change: 1 addition & 0 deletions schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ CREATE TABLE `users` (
`name` varchar(255) NOT NULL DEFAULT '',
`email` varchar(255) NOT NULL DEFAULT '',
`username` varchar(16) DEFAULT NULL,
`github` varchar(39) DEFAULT NULL,
`cvsaccess` int(1) NOT NULL DEFAULT 0,
`spamprotect` int(1) NOT NULL DEFAULT 1,
`forgot` varchar(32) DEFAULT NULL,
Expand Down
Loading