Use a new table (oauth_token_association) to associate authorized

request tokins with OAuth client applications and profiles.
This commit is contained in:
Zach Copley 2010-10-20 17:21:04 -07:00
parent 3d6a0f730d
commit e56385a7bb
6 changed files with 235 additions and 83 deletions

View File

@ -78,6 +78,7 @@ class ApiOauthAccessTokenAction extends ApiOauthAction
$this->reqToken = $req->get_parameter('oauth_token'); $this->reqToken = $req->get_parameter('oauth_token');
$this->verifier = $req->get_parameter('oauth_verifier'); $this->verifier = $req->get_parameter('oauth_verifier');
$app = $datastore->getAppByRequestToken($this->reqToken); $app = $datastore->getAppByRequestToken($this->reqToken);
$atok = $server->fetch_access_token($req); $atok = $server->fetch_access_token($req);
@ -106,7 +107,7 @@ class ApiOauthAccessTokenAction extends ApiOauthAction
common_log( common_log(
LOG_INFO, LOG_INFO,
sprintf( sprintf(
"Issued now access token '%s' for application %d (%s).", "Issued access token '%s' for application %d (%s).",
$atok->key, $atok->key,
$app->id, $app->id,
$app->name $app->name

View File

@ -177,28 +177,24 @@ class ApiOauthAuthorizeAction extends Action
$this->serverError($e->getMessage()); $this->serverError($e->getMessage());
} }
// associated the authorized req token with the user and the app // XXX: Make sure we have a oauth_token_association table. The table
// is now in the main schema, but because it is being added with
// a point release, it's unlikely to be there. This code can be
// removed as of 1.0.
$this->ensureOauthTokenAssociationTable();
$appUser = new Oauth_application_user(); $tokenAssoc = new Oauth_token_association();
$appUser->profile_id = $user->id; $tokenAssoc->profile_id = $user->id;
$appUser->application_id = $this->app->id; $tokenAssoc->application_id = $this->app->id;
$tokenAssoc->token = $this->oauthTokenParam;
$tokenAssoc->created = common_sql_now();
// Note: do not copy the access type from the application. $result = $tokenAssoc->insert();
// The access type should always be 0 when the OAuth app
// user record has a request token associated with it.
// Access type gets assigned once an access token has been
// granted. The OAuth app user record then gets updated
// with the new access token and access type.
$appUser->token = $this->oauthTokenParam;
$appUser->created = common_sql_now();
$result = $appUser->insert();
if (!$result) { if (!$result) {
common_log_db_error($appUser, 'INSERT', __FILE__); common_log_db_error($tokenAssoc, 'INSERT', __FILE__);
$this->serverError(_('Database error inserting OAuth application user.')); $this->serverError(_('Database error inserting oauth_token_association.'));
} }
// If we have a callback redirect and provide the token // If we have a callback redirect and provide the token
@ -265,6 +261,30 @@ class ApiOauthAuthorizeAction extends Action
} }
} }
// XXX Remove this function when we hit 1.0
function ensureOauthTokenAssociationTable()
{
$schema = Schema::get();
$reqTokenCols = array(
new ColumnDef('profile_id', 'integer', null, true, 'PRI'),
new ColumnDef('application_id', 'integer', null, true, 'PRI'),
new ColumnDef('token', 'varchar', 255, true, 'PRI'),
new ColumnDef('created', 'datetime', null, false),
new ColumnDef(
'modified',
'timestamp',
null,
false,
null,
'CURRENT_TIMESTAMP',
'on update CURRENT_TIMESTAMP'
)
);
$schema->ensureTable('oauth_token_association', $reqTokenCols);
}
function showForm($error=null) function showForm($error=null)
{ {
$this->error = $error; $this->error = $error;

View File

@ -0,0 +1,44 @@
<?php
/**
* Table Definition for oauth_association
*/
require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
class Oauth_token_association extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'oauth_token_association'; // table name
public $profile_id; // int(4) primary_key not_null
public $application_id; // int(4) primary_key not_null
public $token; // varchar(255) primary key not null
public $created; // datetime not_null
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
/* Static get */
function staticGet($k, $v = NULL) {
return Memcached_DataObject::staticGet('oauth_token_association', $k, $v);
}
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
static function getByUserAndToken($user, $token)
{
if (empty($user) || empty($token)) {
return null;
}
$oau = new oauth_request_token();
$oau->profile_id = $user->id;
$oau->token = $token;
$oau->limit(1);
$result = $oau->find(true);
return empty($result) ? null : $oau;
}
}

View File

@ -401,6 +401,18 @@ modified = 384
profile_id = K profile_id = K
application_id = K application_id = K
[oauth_token_association]
profile_id = 129
application_id = 129
token = 130
created = 142
modified = 384
[oauth_token_association__keys]
profile_id = K
application_id = K
token = K
[profile] [profile]
id = 129 id = 129
nickname = 130 nickname = 130

View File

@ -237,6 +237,15 @@ create table oauth_application_user (
constraint primary key (profile_id, application_id) constraint primary key (profile_id, application_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table oauth_token_association (
profile_id integer not null comment 'user of the application' references profile (id),
application_id integer not null comment 'id of the application' references oauth_application (id),
token varchar(255) comment 'request or access token',
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified',
constraint primary key (profile_id, application_id, token)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
/* These are used by JanRain OpenID library */ /* These are used by JanRain OpenID library */
create table oid_associations ( create table oid_associations (

View File

@ -32,25 +32,42 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
// Create an anon consumer and anon application if one // Create an anon consumer and anon application if one
// doesn't exist already // doesn't exist already
if ($consumerKey == 'anonymous') { if ($consumerKey == 'anonymous') {
common_debug("API OAuth - creating anonymous consumer");
$con = new Consumer(); $con = new Consumer();
$con->consumer_key = $consumerKey; $con->consumer_key = $consumerKey;
$con->consumer_secret = $consumerKey; $con->consumer_secret = $consumerKey;
$con->created = common_sql_now();
$result = $con->insert(); $result = $con->insert();
if (!$result) { if (!$result) {
$this->serverError(_("Could not create anonymous consumer.")); $this->serverError(_("Could not create anonymous consumer."));
} }
$app = Oauth_application::getByConsumerKey('anonymous');
if (!$app) {
common_debug("API OAuth - creating anonymous application");
$app = new OAuth_application(); $app = new OAuth_application();
$app->owner = 1; // XXX: What to do here?
$app->consumer_key = $con->consumer_key; $app->consumer_key = $con->consumer_key;
$app->name = 'anonymous'; $app->name = 'anonymous';
$app->icon = 'default-avatar-stream.png'; // XXX: Fix this!
$app->description = "An anonymous application";
// XXX: allow the user to set the access type when // XXX: allow the user to set the access type when
// authorizing? Currently we default to r+w for anonymous // authorizing? Currently we default to r+w for anonymous
// OAuth client applications // OAuth client applications
$app->access_type = 3; // read + write $app->access_type = 3; // read + write
$app->type = 2; // desktop
$app->created = common_sql_now();
$id = $app->insert(); $id = $app->insert();
if (!$id) { if (!$id) {
$this->serverError(_("Could not create anonymous OAuth application.")); $this->serverError(_("Could not create anonymous OAuth application."));
} }
}
} else { } else {
return null; return null;
} }
@ -64,10 +81,12 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
function getAppByRequestToken($token_key) function getAppByRequestToken($token_key)
{ {
// Look up the full req tokenx // Look up the full req token
$req_token = $this->lookup_token(null, $req_token = $this->lookup_token(
null,
'request', 'request',
$token_key); $token_key
);
if (empty($req_token)) { if (empty($req_token)) {
common_debug("couldn't get request token from oauth datastore"); common_debug("couldn't get request token from oauth datastore");
@ -85,7 +104,6 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
} }
// Look up the app // Look up the app
$app = new Oauth_application(); $app = new Oauth_application();
$app->consumer_key = $token->consumer_key; $app->consumer_key = $token->consumer_key;
$result = $app->find(true); $result = $app->find(true);
@ -102,12 +120,12 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
{ {
common_debug( common_debug(
sprintf( sprintf(
"%s - New access token from request token %s, consumer %s and verifier %s ", "New access token from request token %s, consumer %s and verifier %s ",
__FILE__,
$token, $token,
$consumer, $consumer,
$verifier $verifier
) ),
__FILE__
); );
$rt = new Token(); $rt = new Token();
@ -121,34 +139,75 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
if ($rt->find(true) && $rt->state == 1 && $rt->verifier == $verifier) { // authorized if ($rt->find(true) && $rt->state == 1 && $rt->verifier == $verifier) { // authorized
common_debug('request token found.'); common_debug('Request token found.', __FILE__);
// find the associated user of the app // find the app and profile associated with this token
$tokenAssoc = OAuth_token_association::staticGet('token', $rt->tok);
if (!$tokenAssoc) {
throw new Exception(
_('Could not find a profile and application associated with the request token.')
);
}
// check to see if we have previously issued an access token for this application
// and profile
$appUser = new Oauth_application_user(); $appUser = new Oauth_application_user();
$appUser->application_id = $app->id; $appUser->application_id = $app->id;
$appUser->token = $rt->tok; $appUser->profile_id = $tokenAssoc->profile_id;
$result = $appUser->find(true); $result = $appUser->find(true);
if (!empty($result)) { if (!empty($result)) {
common_debug("Ouath app user found.");
} else {
common_debug("Oauth app user not found. app id $app->id token $rt->tok");
return null;
}
// go ahead and make the access token common_log(LOG_INFO,
sprintf(
"Existing access token found for application %s, profile %s.",
$app->id,
$tokenAssoc->profile_id
)
);
$at = new Token(); $at = new Token();
// fetch the full access token
$at->consumer_key = $consumer->key;
$at->tok = $appUser->token;
$result = $at->find(true);
if (!$result) {
throw new Exception(
_('Could not issue access token.')
);
}
// Yay, we can re-issue the access token
return new OAuthToken($at->tok, $at->secret);
} else {
common_log(LOG_INFO,
sprintf(
"Creating new access token for application %s, profile %s.",
$app->id,
$tokenAssoc->profile_id
)
);
// make a brand new access token
$at = new Token();
$at->consumer_key = $consumer->key; $at->consumer_key = $consumer->key;
$at->tok = common_good_rand(16); $at->tok = common_good_rand(16);
$at->secret = common_good_rand(16); $at->secret = common_good_rand(16);
$at->type = 1; // access $at->type = 1; // access
$at->verifier = $verifier; $at->verifier = $verifier;
$at->verified_callback = $rt->verified_callback; // 1.0a $at->verified_callback = $rt->verified_callback; // 1.0a
$at->created = DB_DataObject_Cast::dateTime(); $at->created = common_sql_now();
if (!$at->insert()) { if (!$at->insert()) {
$e = $at->_lastError; $e = $at->_lastError;
@ -163,31 +222,38 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
return null; return null;
} }
common_debug('request token "' . $rt->tok . '" updated', __FILE__); common_debug('request token "' . $rt->tok . '" updated', __FILE__);
}
// update the token from req to access for the user // insert a new Oauth_application_user record w/access token
$appUser = new Oauth_application_user();
$orig = clone($appUser);
$appUser->token = $at->tok;
// It's at this point that we change the access type
// to whatever the application's access is. Request
// tokens should always have an access type of 0, and
// therefore be unuseable for making requests for
// protected resources.
$appUser->profile_id = $tokenAssoc->profile_id;;
$appUser->application_id = $app->id;
$appUser->access_type = $app->access_type; $appUser->access_type = $app->access_type;
$appUser->token = $at->tok;
$appUser->created = common_sql_now();
$result = $appUser->updateKeys($orig); $result = $appUser->insert();
if (!$result) { if (!$result) {
throw new Exception('Couldn\'t update OAuth app user.'); common_log_db_error($appUser, 'INSERT', __FILE__);
$this->serverError(_('Database error inserting OAuth application user.'));
} }
// Okay, good // Okay, good
return new OAuthToken($at->tok, $at->secret); return new OAuthToken($at->tok, $at->secret);
} }
} else { } else {
// the token was not authorized or not verfied
common_log(
LOG_INFO,
sprintf(
"API OAuth - Attempt to exchange unauthorized or unverified request token %s for an access token.",
$rt->tok
)
);
return null; return null;
} }
} }