Skip to content

Commit

Permalink
Merge pull request #15 from morgant/github-api-v3-support
Browse files Browse the repository at this point in the history
GitHub API v3 OAuth support with rudimentary rate limiting.
  • Loading branch information
amyreese committed Jun 9, 2012
2 parents 24cade0 + 3a7c56a commit bf506ad
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 29 deletions.
157 changes: 130 additions & 27 deletions SourceGithub/SourceGithub.php 100644 → 100755
Expand Up @@ -83,8 +83,9 @@ public function url_diff( $p_repo, $p_changeset, $p_file ) {
public function update_repo_form( $p_repo ) {
$t_hub_username = null;
$t_hub_reponame = null;
$t_hub_api_login = null;
$t_hub_api_token = null;
$t_hub_app_client_id = null;
$t_hub_app_secret = null;
$t_hub_app_access_token = null;

if ( isset( $p_repo->info['hub_username'] ) ) {
$t_hub_username = $p_repo->info['hub_username'];
Expand All @@ -94,12 +95,16 @@ public function update_repo_form( $p_repo ) {
$t_hub_reponame = $p_repo->info['hub_reponame'];
}

if ( isset( $p_repo->info['hub_api_login'] ) ) {
$t_hub_api_login = $p_repo->info['hub_api_login'];
if ( isset( $p_repo->info['hub_app_client_id'] ) ) {
$t_hub_app_client_id = $p_repo->info['hub_app_client_id'];
}

if ( isset( $p_repo->info['hub_api_token'] ) ) {
$t_hub_api_token = $p_repo->info['hub_api_token'];
if ( isset( $p_repo->info['hub_app_secret'] ) ) {
$t_hub_app_secret = $p_repo->info['hub_app_secret'];
}

if ( isset( $p_repo->info['hub_app_access_token'] ) ) {
$t_hub_app_access_token = $p_repo->info['hub_app_access_token'];
}

if ( isset( $p_repo->info['master_branch'] ) ) {
Expand All @@ -117,12 +122,22 @@ public function update_repo_form( $p_repo ) {
<td><input name="hub_reponame" maxlength="250" size="40" value="<?php echo string_attribute( $t_hub_reponame ) ?>"/></td>
</tr>
<tr <?php echo helper_alternate_class() ?>>
<td class="category"><?php echo plugin_lang_get( 'hub_api_login' ) ?></td>
<td><input name="hub_api_login" maxlength="250" size="40" value="<?php echo string_attribute( $t_hub_api_login ) ?>"/></td>
<td class="category"><?php echo plugin_lang_get( 'hub_app_client_id' ) ?></td>
<td><input name="hub_app_client_id" maxlength="250" size="40" value="<?php echo string_attribute( $t_hub_app_client_id ) ?>"/></td>
</tr>
<tr <?php echo helper_alternate_class() ?>>
<td class="category"><?php echo plugin_lang_get( 'hub_app_secret' ) ?></td>
<td><input name="hub_app_secret" maxlength="250" size="40" value="<?php echo string_attribute( $t_hub_app_secret ) ?>"/></td>
</tr>
<tr <?php echo helper_alternate_class() ?>>
<td class="category"><?php echo plugin_lang_get( 'hub_api_token' ) ?></td>
<td><input name="hub_api_token" maxlength="250" size="40" value="<?php echo string_attribute( $t_hub_api_token ) ?>"/></td>
<td class="category"><?php echo plugin_lang_get( 'hub_app_access_token' ) ?></td>
<td><?php if ( empty( $t_hub_app_client_id ) || empty( $t_hub_app_secret ) ):
echo plugin_lang_get( 'hub_app_client_id_secret_missing' );
elseif ( empty( $t_hub_app_access_token ) ):
print_link( $this->oauth_authorize_uri( $p_repo ), plugin_lang_get( 'hub_app_authorize' ) );
else:
echo plugin_lang_get( 'hub_app_authorized' );
endif; ?></td>
</tr>
<tr <?php echo helper_alternate_class() ?>>
<td class="category"><?php echo plugin_lang_get( 'master_branch' ) ?></td>
Expand All @@ -134,8 +149,8 @@ public function update_repo_form( $p_repo ) {
public function update_repo( $p_repo ) {
$f_hub_username = gpc_get_string( 'hub_username' );
$f_hub_reponame = gpc_get_string( 'hub_reponame' );
$f_hub_api_login = gpc_get_string( 'hub_api_login' );
$f_hub_api_token = gpc_get_string( 'hub_api_token' );
$f_hub_app_client_id = gpc_get_string( 'hub_app_client_id' );
$f_hub_app_secret = gpc_get_string( 'hub_app_secret' );
$f_master_branch = gpc_get_string( 'master_branch' );

if ( !preg_match( '/\*|^[a-zA-Z0-9_\., -]*$/', $f_master_branch ) ) {
Expand All @@ -145,29 +160,46 @@ public function update_repo( $p_repo ) {

$p_repo->info['hub_username'] = $f_hub_username;
$p_repo->info['hub_reponame'] = $f_hub_reponame;
$p_repo->info['hub_api_login'] = $f_hub_api_login;
$p_repo->info['hub_api_token'] = $f_hub_api_token;
$p_repo->info['hub_app_client_id'] = $f_hub_app_client_id;
$p_repo->info['hub_app_secret'] = $f_hub_app_secret;
$p_repo->info['master_branch'] = $f_master_branch;

return $p_repo;
}

private function api_uri( $p_repo, $p_path ) {
$t_uri = 'https://api.github.com/' . $p_path;

if ( !is_blank( $p_repo->info['hub_api_token'] ) ) {
$t_token = $p_repo->info['hub_api_token'];
$t_login = $p_repo->info['hub_username'];

if ( !is_blank( $p_repo->info['hub_api_login'] ) ) {
$t_login = $p_repo->info['hub_api_login'];
}

$t_uri .= '?login=' . $t_login . '&token=' . $t_token;

$t_access_token = $p_repo->info['hub_app_access_token'];
if ( !is_blank( $t_access_token ) ) {
$t_uri .= '?access_token=' . $t_access_token;
}

return $t_uri;
}

private function api_json_url( $p_repo, $p_url, $p_member = null ) {
static $t_start_time;
if ( $t_start_time === null ) {
$t_start_time = microtime( true );
} else if ( ( microtime( true ) - $t_start_time ) >= 3600.0 ) {
$t_start_time = microtime( true );
}

$t_uri = $this->api_uri( $p_repo, 'rate_limit' );
$t_json = json_url( $t_uri, 'rate' );
if ( false !== $t_json && !is_null( $t_json ) ) {
if ( $t_json->remaining <= 0 ) {
// do we need to do something here?
} else if ( $t_json->remaining < ( $t_json->limit / 2 ) ) {
$t_time_remaining = 3600.0 - ( microtime( true ) - $t_start_time );
$t_sleep_time = ( $t_time_remaining / $t_json->remaining ) * 1000000;
usleep( $t_sleep_time );
}
}

return json_url( $p_url, $p_member );
}

public function precommit() {
$f_payload = gpc_get_string( 'payload', null );
Expand Down Expand Up @@ -235,7 +267,7 @@ public function import_full( $p_repo ) {
$t_reponame = $p_repo->info['hub_reponame'];

$t_uri = $this->api_uri( $p_repo, "repos/$t_username/$t_reponame/branches" );
$t_json = json_url( $t_uri );
$t_json = $this->api_json_url( $p_repo, $t_uri );

$t_branches = array();
foreach ($t_json as $t_branch)
Expand Down Expand Up @@ -296,7 +328,7 @@ public function import_commits( $p_repo, $p_commit_ids, $p_branch='' ) {

echo "Retrieving $t_commit_id ... ";
$t_uri = $this->api_uri( $p_repo, "repos/$t_username/$t_reponame/commits/$t_commit_id" );
$t_json = json_url( $t_uri );
$t_json = $this->api_json_url( $p_repo, $t_uri );

if ( false === $t_json || is_null( $t_json ) ) {
echo "failed.\n";
Expand Down Expand Up @@ -361,4 +393,75 @@ private function json_commit_changeset( $p_repo, $p_json, $p_branch='' ) {
return array( null, array() );
}
}
}

private function oauth_authorize_uri( $p_repo ) {
$t_hub_app_client_id = null;
$t_hub_app_secret = null;
$t_hub_app_access_token = null;

if ( isset( $p_repo->info['hub_app_client_id'] ) ) {
$t_hub_app_client_id = $p_repo->info['hub_app_client_id'];
}

if ( isset( $p_repo->info['hub_app_secret'] ) ) {
$t_hub_app_secret = $p_repo->info['hub_app_secret'];
}

if ( !empty( $t_hub_app_client_id ) && !empty( $t_hub_app_secret ) ) {
return 'https://github.com/login/oauth/authorize?client_id=' . $t_hub_app_client_id . '&redirect_uri=' . urlencode(config_get('path') . 'plugin.php?page=SourceGithub/oauth_authorize&id=' . $p_repo->id ) . '&scope=repo';
} else {
return '';
}
}

public static function oauth_get_access_token( $p_repo, $p_code ) {
# build the GitHub URL & POST data
$t_url = 'https://github.com/login/oauth/access_token';
$t_post_data = array( 'client_id' => $p_repo->info['hub_app_client_id'],
'client_secret' => $p_repo->info['hub_app_secret'],
'code' => $p_code );
$t_data = self::url_post( $t_url, $t_post_data );

$t_access_token = '';
if ( !empty( $t_data ) ) {
$t_response = array();
parse_str( $t_data, $t_response );
if ( isset( $t_response['access_token'] ) === true ) {
$t_access_token = $t_response['access_token'];
}
}

if ( !empty( $t_access_token ) ) {
if ( $t_access_token != $p_repo->info['hub_app_access_token'] ) {
$p_repo->info['hub_app_access_token'] = $t_access_token;
$p_repo->save();
}
return true;
} else {
return false;
}
}

public static function url_post( $p_url, $p_post_data ) {
$t_post_data = http_build_query( $p_post_data );

# Use the PHP cURL extension
if( function_exists( 'curl_init' ) ) {
$t_curl = curl_init( $p_url );
curl_setopt( $t_curl, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $t_curl, CURLOPT_POST, true );
curl_setopt( $t_curl, CURLOPT_POSTFIELDS, $t_post_data );

$t_data = curl_exec( $t_curl );
curl_close( $t_curl );

return $t_data;
} else {
# Last resort system call
$t_url = escapeshellarg( $p_url );
$t_post_data = escapeshellarg( $t_post_data );
return shell_exec( 'curl ' . $t_url . ' -d ' . $t_post_data );
}
}

}
15 changes: 13 additions & 2 deletions SourceGithub/lang/strings_english.txt
Expand Up @@ -10,6 +10,17 @@ $s_plugin_SourceGithub_description = 'Adds GitHub integration to the Source Inte

$s_plugin_SourceGithub_hub_username = 'GitHub Username';
$s_plugin_SourceGithub_hub_reponame = 'GitHub Repository<br/><span class="small">(lowercase, dashed name)</span>';
$s_plugin_SourceGithub_hub_api_login = 'GitHub API Login<br/><span class="small">For private repositories</span>';
$s_plugin_SourceGithub_hub_api_token = 'GitHub API Token<br/><span class="small">For private repositories</span>';
$s_plugin_SourceGithub_hub_app_client_id = 'GitHub Application Client ID<br /><span class="small">For private repositories. Create a new <a href="https://github.com/settings/applications">GitHub Application</a> if needed</span>';
$s_plugin_SourceGithub_hub_app_secret = 'GitHub Application Secret<br /><span class="small">For private repositories</span>';
$s_plugin_SourceGithub_hub_app_access_token = 'GitHub Application Access Token<br /><span class="small">For private repositories</span>';
$s_plugin_SourceGithub_master_branch = 'Primary Branches<br/><span class="small">(comma-separated list)</span>';

$s_plugin_SourceGithub_hub_app_client_id_secret_missing = 'N/A<br /><span class="small">You must enter the GitHub Application Client ID &amp; Secret and update before you can authorize</span>';
$s_plugin_SourceGithub_hub_app_authorize = 'Click to Authorize';
$s_plugin_SourceGithub_hub_app_authorized = 'Authorized';

$s_plugin_SourceGithub_repo_authorized = '<p>MantisBT is now authorized to access this GitHub repository.</p>';
$s_plugin_SourceGithub_repo_authorization_failed = '<p style="color: red;">Sorry, MantisBT could not be authorized to access this GitHub repository.</p>';

$s_plugin_SourceGithub_oauth_authorization = 'GitHub OAuth Authorization';
$s_plugin_SourceGithub_back_repo = 'Back to Repository';
39 changes: 39 additions & 0 deletions SourceGithub/pages/oauth_authorize.php
@@ -0,0 +1,39 @@
<?php

//require_once( config_get( 'plugin_path' ) . 'SourceGithub/SourceGithub.php' );

auth_reauthenticate();

html_page_top1( plugin_lang_get( 'title' ) );
html_page_top2();

print_manage_menu();

$f_repo_id = gpc_get_int( 'id' );
$f_code = gpc_get_string( 'code' );

$t_repo = SourceRepo::load( $f_repo_id );
if ( SourceGithubPlugin::oauth_get_access_token( $t_repo, $f_code ) === true ) {
$t_was_authorized = true;
} else {
$t_was_authorized = false;
}
?>

<table class="width60" align="center" cellspacing="1">

<tr>
<td class="form-title"><?php echo plugin_lang_get( 'oauth_authorization' ) ?></td>
<td class="right"><?php print_bracket_link( plugin_page( 'repo_manage_page', false, 'Source' ) . '&id=' . $t_repo->id, plugin_lang_get( 'back_repo' ) ) ?></td>
</tr>

<tr>
<td class="center" colspan="2"><?php echo $t_was_authorized === true ? plugin_lang_get('repo_authorized') : plugin_lang_get('repo_authorization_failed'); ?></td>
</tr>

</table>

<?php
html_page_bottom1( __FILE__ );

?>

0 comments on commit bf506ad

Please sign in to comment.