diff --git includes/VersioncontrolRepository.php includes/VersioncontrolRepository.php index 0d8097c..575440c 100644 --- includes/VersioncontrolRepository.php +++ includes/VersioncontrolRepository.php @@ -99,6 +99,7 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface * The current plugin types(array keys) are: * - author_mapper * - committer_mapper + * - auth_handler * * @var array */ @@ -476,6 +477,19 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface return new $class_name(); } + public function getAuthHandler() { + if (!isset($this->pluginInstances['auth_handler'])) { + // If no plugin is set, use the free-for-all plugin + if (empty($this->plugins['auth_handler'])) { + // FIXME temporarily writing to a db-recorded field like this is very hacky + $this->plugins['auth_handler'] = 'ffa'; + } + $this->pluginInstances['auth_handler'] = $this->getPluginClass('auth_handler', 'vcs_auth', 'handler'); + $this->pluginInstances['auth_handler']->setRepository($this); + } + return $this->pluginInstances['auth_handler']; + } + public function getAuthorMapper() { if (!isset($this->pluginInstances['author_mapper'])) { // if no plugin is set, just directly register FALSE for the instance diff --git includes/interfaces.inc includes/interfaces.inc index 3d0114e..0ff526e 100644 --- includes/interfaces.inc +++ includes/interfaces.inc @@ -148,7 +148,7 @@ interface VersioncontrolRepositoryGetItem { * repository/path/revision combination is always unique, so no * additional information is needed. * - 'label': A label array with at least 'name' and 'type' elements - * filled in. If a label is provided, it should be incorporated + * filled in. If a label is specified, it should be incorporated * into the result item as 'selected_label' (see return value * docs), and will cause the most recent item on the label to * be fetched. If the label includes an additional 'date' @@ -203,4 +203,112 @@ interface VersioncontrolUserMapperInterface { * Either a uid (int), or FALSE if the mapping failed. */ public function mapCommitter(VersioncontrolOperation $commit); -} \ No newline at end of file +} + +interface VersioncontrolAuthHandlerInterface { + public function setRepository(VersioncontrolRepository $repository); + + /** + * Determine whether the specified user has any access at all to the + * repository. + * + * @param int $uid + * The uid of the Drupal user to be checked. + * + * @return bool + * Boolean indicating access approved (TRUE) or denied (FALSE) + */ + public function authAccess($uid); + + /** + * Determine whether the specified user has access to create new branches in + * the repository. + * + * @param int $uid + * The uid of the Drupal user to be checked. + * + * @return bool + * Boolean indicating access approved (TRUE) or denied (FALSE) + */ + public function authBranchCreate($uid); + + /** + * Determine whether the specified user has access to delete the specified + * branch in the repository. + * + * @param int $uid + * The uid of the Drupal user to be checked. + * @param VersioncontrolBranch $branch + * The VersioncontrolBranch object representing the branch against which + * authorization checks should be made. + * + * @return bool + * Boolean indicating access approved (TRUE) or denied (FALSE) + */ + public function authBranchDelete($uid, VersioncontrolBranch $branch); + + /** + * Determine whether the specified user has access to write to the specified + * branch in the repository. + * + * @param int $uid + * The uid of the Drupal user to be checked. + * @param VersioncontrolBranch $branch + * The VersioncontrolBranch object representing the branch against which + * authorization checks should be made. + * + * @return bool + * Boolean indicating access approved (TRUE) or denied (FALSE) + */ + public function authBranchUpdate($uid, VersioncontrolBranch $branch); + + /** + * Determine whether the specified user has access to create new tags in the + * repository. + * + * @param int $uid + * The uid of the Drupal user to be checked. + * @return bool + * Boolean indicating access approved (TRUE) or denied (FALSE) + */ + public function authTagCreate($uid); + + /** + * Determine whether the specified user has access to delete the specified + * tag in the repository. + * + * @param int $uid + * The uid of the Drupal user to be checked. + * @param VersioncontrolTag $tag + * The VersioncontrolTag object representing the tag against which + * authorization checks should be made. + * + * @return bool + * Boolean indicating access approved (TRUE) or denied (FALSE) + */ + public function authTagDelete($uid, VersioncontrolTag $tag); + + /** + * Determine whether the specified user has access to update or modify + * the specified tag in the repository. + * + * @param int $uid + * The uid of the Drupal user to be checked. + * @param VersioncontrolTag $tag + * The VersioncontrolTag object representing the tag against which + * authorization checks should be made. + * + * @return bool + * Boolean indicating access approved (TRUE) or denied (FALSE) + */ + public function authTagUpdate($uid, VersioncontrolTag $tag); + + /** + * Retrieve any errors messages that have been enqueued during auth checking. + * + * Most of the authorization methods will enqueue messages to indicate the + * reason for rejecting access. These messages may be useful for logging, or + * to provide as feedback to the user. + */ + public function getErrorMessages(); +} diff --git includes/plugins/vcs_auth/VersioncontrolAuthHandlerFFA.class.php includes/plugins/vcs_auth/VersioncontrolAuthHandlerFFA.class.php new file mode 100644 index 0000000..4578f79 --- /dev/null +++ includes/plugins/vcs_auth/VersioncontrolAuthHandlerFFA.class.php @@ -0,0 +1,32 @@ +repository instanceof VersioncontrolRepository && $this->repository !== $repository) { + throw new Exception('Cannot attach different repositories to a single VersioncontrolAuthHandlerMappedAccounts instance. Instanciate a new object.', E_RECOVERABLE_ERROR); + } + + $this->repository = $repository; + $this->build(); + } + + protected function build() { + if ($this->built) { + return; // already built, bail out + } + if (!$this->repository instanceof VersioncontrolRepository) { + throw new Exception('Cannot build the account mapper object until a repository has been attached.'); + } + + // Retrieve the base auth data + $this->userData = db_select('versioncontrol_auth_account', 'base') + ->fields('base') + ->condition('repo_id', $this->repository->repo_id) + ->execute() + ->fetchAllAssoc('uid', PDO::FETCH_ASSOC); + + foreach ($this->userData as &$data) { + $data['per-label'] = array(); + } + + // Retrieve the extended per-label auth data + $label_data = db_select('versioncontrol_auth_account_label', 'base') + ->fields('base') + ->condition('repo_id', $this->repository->repo_id) + ->execute(); + + foreach ($label_data as $row) { + $labeldata = array( + 'label_update' => $row->label_update, + 'label_delete' => $row->label_delete, + ); + $this->userData[$row->uid]['per-label'][$row->label_id] = $labeldata; + } + + $this->built = TRUE; + } + + public function authAccess($uid) { + $this->build(); + if (empty($this->userData[$uid]) || empty($this->userData[$uid]['access'])) { + // No account is registered, or access is set to 0 on the account + $this->errors[] = t('User does not have access to this repository.'); + return FALSE; + } + return TRUE; + } + + /** + * Helper method - determine the base level of access to the repository for + * the specified user. + * + * @param int $uid + * The uid of the Drupal user to be checked. + * @return int + * The base access level for the specified user, or 0 if not found. + */ + protected function baseAuth($uid) { + $this->build(); + if (empty($this->userData[$uid])) { + // If no record of the user, deny. + return self::DENY; + } + else { + return (int) $this->userData[$uid]['access']; + } + } + + public function authBranchCreate($uid) { + $base = $this->baseAuth($uid); + if ($base == self::DENY) { + // Zero access, deny. + return FALSE; + } + else if ($base == self::ALL) { + // User has super cow powers, say yes. + return TRUE; + } + + return $this->userData[$uid]['branch_create'] == self::GRANT; + } + + public function authBranchDelete($uid, VersioncontrolBranch $branch) { + return $this->authLabel($uid, $branch, 'delete'); + } + + public function authBranchUpdate($uid, VersioncontrolBranch $branch) { + return $this->authLabel($uid, $branch, 'update'); + } + public function authTagCreate($uid) { + $base = $this->baseAuth($uid); + if ($base == self::DENY) { + // Zero access, deny. + return FALSE; + } + else if ($base == self::ALL) { + // User has super cow powers, say yes. + return TRUE; + } + + return $this->userData[$uid]['tag_create'] == self::GRANT; + } + public function authTagDelete($uid, VersioncontrolTag $tag) { + return $this->authLabel($uid, $tag, 'delete'); + } + public function authTagUpdate($uid, VersioncontrolTag $tag) { + return $this->authLabel($uid, $tag, 'update'); + } + + /** + * Perform an authorization check on a specified user against a specified + * label for a specified op. + * + * This is just a shared helper method for the branch/tag update/delete + * methods, as they all have virtually identical logical flow. + * + * @param int $uid + * The uid of the Drupal user to be checked. + * @param VersioncontrolEntity $label + * Either a VersioncontrolTag or VersioncontrolBranch object, representing + * the label against which authorization checks should be made. + * @param string $op + * Either 'update' or 'delete'. + * + * @return bool + * Boolean indicating access approved (TRUE) or denied (FALSE) + */ + protected function authLabel($uid, VersioncontrolEntity $label, $op) { + $base = $this->baseAuth($uid); + switch ($base) { + case self::DENY: + // Zero access, deny. + return FALSE; + case self::ALL: + // User has super cow powers, say yes. + return TRUE; + } + + $type = $label instanceof VersioncontrolTag ? 'tag' : 'branch'; + + switch ($this->userData[$uid][$type . '_' . $op]) { + case self::DENY: + // User has no perms for this op on this label type + return FALSE; + case self::ALL: + // User has all perms for this op on this label type + return TRUE; + } + + // If we get this far, then we're doing a label-specific perm check. + return $this->userData[$uid]['per-label'][$label->label_id]['label_' . $op] == self::GRANT; + } + + public function getErrorMessages() { + return $this->errors; + } + + /** + * Set the permissons on this repository for the specified user. + * + * @param int $uid + * The uid to which the permissions data should be assigned. + * + * @param array $data + * The array of permissions data to be assigned. + */ + public function setUserData($uid, $data) { + $this->userData[$uid] = $data; + } + + /** + * Retrieve the data representing a particular user's permission set, or the + * entire set of permissions that have been set up for this repository. + * + * @param mixed $uid + * Permissions data will be retrieved for this uid. If not provided, all + * permissions data is returned. + * + * @return mixed + * An array of perm data for the requested user, or an array of such arrays + * keyed on uid. If an invalid user is requested, returns FALSE. + */ + public function getUserData($uid = NULL) { + if (is_null($uid)) { + return $this->userData; + } + else { + return empty($this->userData[$uid]) ? FALSE : $this->userData[$uid]; + } + } + + /** + * Save all auth information for the attached repository into the db. + * + * This operates by simply blowing away all data and rewriting it with mass + * inserts, making it more performant overall but also leaving a very, very + * small window during which auths may fail because the data is unavailable. + */ + public function save() { + if (!isset($this->repository)) { + throw new Exception('Cannot save auth data without a repository to attach to.', E_ERROR); + } + db_delete('versioncontrol_auth_account') + ->condition('repo_id', $this->repository->repo_id); + db_delete('versioncontrol_auth_account_label') + ->condition('repo_id', $this->repository->repo_id); + + // Prepare values + $base_values = array(); + $per_label_values = array(); + foreach ($this->userData as $uid => $data) { + $data['uid'] = $uid; + $data['repo_id'] = $this->repository->repo_id; + + foreach ($data['per-label'] as $label_id => $label_data) { + $label_data['uid'] = $uid; + $label_data['repo_id'] = $this->repository->repo_id; + $label_data['label_id'] = $label_id; + + $per_label_values[] = $label_data; + } + + unset($data['per-label']); + $base_values[] = $data; + } + + // Perform base insert + $fields = array('uid', 'repo_id', 'access', 'branch_create', + 'branch_update', 'branch_delete', 'tag_create', 'tag_update', + 'tag_delete'); + + $insert = db_insert('versioncontrol_auth_account')->fields($fields); + + foreach ($base_values as $record) { + $insert->values($record); + } + $insert->execute(); + + $fields = array('uid', 'repo_id', 'label_id', 'label_update', 'label_delete'); + $insert = db_insert('versioncontrol_auth_account_label')->fields($fields); + + foreach ($per_label_values as $record) { + $insert->values($record); + } + $insert->execute(); + } +} \ No newline at end of file diff --git includes/plugins/vcs_auth/account.inc includes/plugins/vcs_auth/account.inc new file mode 100644 index 0000000..bb01820 --- /dev/null +++ includes/plugins/vcs_auth/account.inc @@ -0,0 +1,8 @@ + t('Free For All (unrestricted write access)'), + 'handler' => array( + 'class' => 'VersioncontrolAuthHandlerMappedAccounts', + ), +); diff --git includes/plugins/vcs_auth/ffa.inc includes/plugins/vcs_auth/ffa.inc new file mode 100644 index 0000000..f31414b --- /dev/null +++ includes/plugins/vcs_auth/ffa.inc @@ -0,0 +1,8 @@ + t('Free For All (unrestricted write access)'), + 'handler' => array( + 'class' => 'VersioncontrolAuthHandlerFFA', + ), +); \ No newline at end of file diff --git tests/VersioncontrolAccountAuthPlugin.test tests/VersioncontrolAccountAuthPlugin.test new file mode 100644 index 0000000..dd4d842 --- /dev/null +++ tests/VersioncontrolAccountAuthPlugin.test @@ -0,0 +1,171 @@ + t('Versioncontrol account authentication plugin testing'), + 'description' => t("Test the base 'account' auth plugin's CRUD and authorization logic."), + 'group' => t('Version Control'), + ); + } + + /** + * Implementation of setUp(). + */ + function setUp() { + $this->useBackends = self::BACKENDS_ALL; + parent::setUp(); + + // Create and login the admin user + $admin_user = $this->drupalCreateUser(array('administer version control systems')); + $this->drupalLogin($admin_user); + + $repo_data = array( + 'plugins' => array( + 'auth_handler' => 'account' + ), + ); + + foreach ($this->backends as $backend_machine_name => $backend) { + $repo = $this->versioncontrolCreateRepository($backend_machine_name, $repo_data); + $this->repos[$backend_machine_name] = $repo; + // add some more dummy data related to each repo + $label_default_data = array('repo_id' => $repo->repo_id); + $this->branches[$repo->repo_id][0] = $this->versioncontrolCreateBranch($backend_machine_name, $label_default_data); + $this->branches[$repo->repo_id][1] = $this->versioncontrolCreateBranch($backend_machine_name, $label_default_data); + $this->branches[$repo->repo_id][2] = $this->versioncontrolCreateBranch($backend_machine_name, $label_default_data); + $this->branches[$repo->repo_id][3] = $this->versioncontrolCreateBranch($backend_machine_name, $label_default_data); + $this->tags[$repo->repo_id][0] = $this->versioncontrolCreateTag($backend_machine_name, $label_default_data); + $this->tags[$repo->repo_id][1] = $this->versioncontrolCreateTag($backend_machine_name, $label_default_data); + $this->tags[$repo->repo_id][2] = $this->versioncontrolCreateTag($backend_machine_name, $label_default_data); + $this->tags[$repo->repo_id][3] = $this->versioncontrolCreateTag($backend_machine_name, $label_default_data); + } + } + + /** + * Helper to get user data. + */ + protected function getMappedAccountsAuthPluginUserData($repo_id) { + return array( + 'access' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + 'branch_create' => VersioncontrolAuthHandlerMappedAccounts::DENY, + 'branch_update' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + 'branch_delete' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + 'tag_create' => VersioncontrolAuthHandlerMappedAccounts::DENY, + 'tag_update' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + 'tag_delete' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + 'per-label' => array( + $this->branches[$repo_id][0]->label_id => array( + 'label_update' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + 'label_delete' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + ), + $this->branches[$repo_id][1]->label_id => array( + 'label_update' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + 'label_delete' => VersioncontrolAuthHandlerMappedAccounts::DENY, + ), + $this->branches[$repo_id][2]->label_id => array( + 'label_update' => VersioncontrolAuthHandlerMappedAccounts::DENY, + 'label_delete' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + ), + $this->branches[$repo_id][3]->label_id => array( + 'label_update' => VersioncontrolAuthHandlerMappedAccounts::DENY, + 'label_delete' => VersioncontrolAuthHandlerMappedAccounts::DENY, + ), + $this->tags[$repo_id][0]->label_id => array( + 'label_update' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + 'label_delete' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + ), + $this->tags[$repo_id][1]->label_id => array( + 'label_update' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + 'label_delete' => VersioncontrolAuthHandlerMappedAccounts::DENY, + ), + $this->tags[$repo_id][2]->label_id => array( + 'label_update' => VersioncontrolAuthHandlerMappedAccounts::DENY, + 'label_delete' => VersioncontrolAuthHandlerMappedAccounts::GRANT, + ), + $this->tags[$repo_id][3]->label_id => array( + 'label_update' => VersioncontrolAuthHandlerMappedAccounts::DENY, + 'label_delete' => VersioncontrolAuthHandlerMappedAccounts::DENY, + ), + ), + ); + } + + public function testMappedAccountsAuthPluginCrud() { + foreach ($this->repos as $repo) { + // Manually instantiate the plugin for the create portion + $class_name = ctools_plugin_load_class('versioncontrol', 'vcs_auth', 'account', 'handler'); + $authplug = new $class_name(); + $authplug->setRepository($repo); + + // Add one auth account row + $super_user = $this->drupalCreateUser(); + $super_user_data = $this->getMappedAccountsAuthPluginUserData($repo->repo_id); + $authplug->setUserData($super_user->uid, $super_user_data); + $authplug->save(); + + // Now load the plugin using the provided method and retrieve the user data, + // ensure it's what we sent in + $db_authplug = $repo->getAuthHandler(); + $db_data = $db_authplug->getUserData($super_user->uid); + $at_input = array_diff($super_user_data, $db_data); + $this->assertTrue(empty($at_input), 'Authentication account database data is included on provided data'); + // add uid for the comparison(db info autoreturns it, but we set it at setUserData() not in the array passed) + $super_user_data['uid'] = $super_user->uid; + $at_db = array_diff($db_data, $super_user_data); + $this->assertTrue(empty($at_db), 'Authentication account provided data is included on database data'); + + // Now try test logic + $authplug = $repo->getAuthHandler(); + $branches = $repo->loadBranches(array( + $this->branches[$repo->repo_id][0]->label_id, + $this->branches[$repo->repo_id][1]->label_id, + $this->branches[$repo->repo_id][2]->label_id, + $this->branches[$repo->repo_id][3]->label_id, + )); + $tags = $repo->loadTags(array( + $this->tags[$repo->repo_id][0]->label_id, + $this->tags[$repo->repo_id][1]->label_id, + $this->tags[$repo->repo_id][2]->label_id, + $this->tags[$repo->repo_id][3]->label_id, + )); + // global + $this->assertTrue($authplug->authAccess($super_user->uid), 'global auth validate correctly'); + // branches + $this->assertFalse($authplug->authBranchCreate($super_user->uid), 'branch create validate correctly'); + $this->assertFalse($authplug->authTagCreate($super_user->uid), 'tag create validate correctly'); + $this->assertTrue($authplug->authBranchUpdate($super_user->uid, $this->branches[$repo->repo_id][0]), 'granular branch update validate correctly'); + $this->assertTrue($authplug->authBranchDelete($super_user->uid, $this->branches[$repo->repo_id][0]), 'granular branch delete validate correctly'); + $this->assertTrue($authplug->authBranchUpdate($super_user->uid, $this->branches[$repo->repo_id][1]), 'granular branch update validate correctly'); + $this->assertFalse($authplug->authBranchDelete($super_user->uid, $this->branches[$repo->repo_id][1]), 'granular branch delete validate correctly'); + $this->assertFalse($authplug->authBranchUpdate($super_user->uid, $this->branches[$repo->repo_id][2]), 'granular branch update validate correctly'); + $this->assertTrue($authplug->authBranchDelete($super_user->uid, $this->branches[$repo->repo_id][2]), 'granular branch delete validate correctly'); + $this->assertFalse($authplug->authBranchUpdate($super_user->uid, $this->branches[$repo->repo_id][3]), 'granular branch update validate correctly'); + $this->assertFalse($authplug->authBranchDelete($super_user->uid, $this->branches[$repo->repo_id][3]), 'granular branch delete validate correctly'); + // tags + $this->assertTrue($authplug->authTagUpdate($super_user->uid, $this->tags[$repo->repo_id][0]), 'granular tag update validate correctly'); + $this->assertTrue($authplug->authTagDelete($super_user->uid, $this->tags[$repo->repo_id][0]), 'granular tag delete validate correctly'); + $this->assertTrue($authplug->authTagUpdate($super_user->uid, $this->tags[$repo->repo_id][1]), 'granular tag update validate correctly'); + $this->assertFalse($authplug->authTagDelete($super_user->uid, $this->tags[$repo->repo_id][1]), 'granular tag delete validate correctly'); + $this->assertFalse($authplug->authTagUpdate($super_user->uid, $this->tags[$repo->repo_id][2]), 'granular tag update validate correctly'); + $this->assertTrue($authplug->authTagDelete($super_user->uid, $this->tags[$repo->repo_id][2]), 'granular tag delete validate correctly'); + $this->assertFalse($authplug->authTagUpdate($super_user->uid, $this->tags[$repo->repo_id][3]), 'granular tag update validate correctly'); + $this->assertFalse($authplug->authTagDelete($super_user->uid, $this->tags[$repo->repo_id][3]), 'granular tag delete validate correctly'); + } + } +} diff --git tests/VersioncontrolTestCase.test tests/VersioncontrolTestCase.test index b04f8a5..7ee9d22 100644 --- tests/VersioncontrolTestCase.test +++ tests/VersioncontrolTestCase.test @@ -121,4 +121,69 @@ abstract class VersioncontrolTestCase extends DrupalWebTestCase { $this->testBackend = versioncontrol_get_backends('test'); } } + + /** + * Create a dummy backend, insert it in the database, and return it for use. + * + * This uses a fake path that doesn't point to any real repository, so + * anything that actually tries to interact with the underlying repo will + * fail. + * + * @param string $backend_name + * @param array $data + */ + public function versioncontrolCreateRepository($backend_name = 'test', $data = array()) { + static $i = 0; + $default_data = array( + 'name' => 'test_repo_' . ++$i, + 'vcs' => $backend_name, + 'root' => '/fake/path/to/repo', + 'update_method' => 0, + 'updated' => 0, + 'locked' => 0, + 'data' => array(), + 'plugins' => array(), + ); + $default_plugins = array( + 'auth_handler' => 'ffa', + ); + + $data = array_merge_recursive($default_data, $data); + foreach ($default_plugins as $plugin_slot => $default_plugin) { + if (empty($data['plugins'][$plugin_slot])) { + $data['plugins'][$plugin_slot] = $default_plugin; + } + } + $backend = $this->backends[$backend_name]; + $repo = $backend->buildEntity('repo', $data); + $repo->insert(); + + return $repo; + } + + public function versioncontrolCreateLabel($type, $backend_name = 'test', $data = array()) { + $default_data = array( + 'name' => $this->randomName(32), + ); + $data += $default_data; + + $backend = $this->backends[$backend_name]; + if (!isset($data['repo_id'])) { + $repo = $this->versioncontrolCreateRepository($backend_name); + $data['repo_id'] = $repo->repo_id; + } + $label = $backend->buildEntity($type, $data); + $label->insert(); + + return $label; + } + + public function versioncontrolCreateBranch($backend_name = 'test', $data = array()) { + return $this->versioncontrolCreateLabel('branch', $backend_name, $data); + } + + public function versioncontrolCreateTag($backend_name = 'test', $data = array()) { + return $this->versioncontrolCreateLabel('tag', $backend_name, $data); + } + } diff --git versioncontrol.install versioncontrol.install index 7bf0329..7004831 100644 --- versioncontrol.install +++ versioncontrol.install @@ -402,6 +402,115 @@ function versioncontrol_schema() { 'primary key' => array('uid', 'repo_id'), ); + $schema['versioncontrol_auth_account'] = array( + 'description' => 'ACL table, used by the VersioncontrolAuthHandlerMappedAccounts family of plugins, that stores ACL data on a per-uid/per-repo basis.', + 'fields' => array( + 'uid' => array( + 'description' => 'Foreign key to {users}.uid; uniquely identifies a Drupal user to whom this ACL data applies.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'repo_id' => array( + 'description' => 'Foreign key to {versioncontrol_repositories}.repo_id; identifies the repository to which this ACL data applies.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'access' => array( + 'type' => 'int', + 'description' => 'Base, global access to the repository. 0 indicates no access (disabled/inactive account; acts as a global deny), 1 indicates some level of access, 2 indicates global access (overrides granular access).', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'branch_create' => array( + 'type' => 'int', + 'description' => 'Grant user access to create branches in the repository.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'branch_update' => array( + 'type' => 'int', + 'description' => 'Grant user access to update/write to any branch in the repository. 1 is global access, 0 defers to individual branch perms.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'branch_delete' => array( + 'type' => 'int', + 'description' => 'Grant user access to delete any branch in the repository. 1 is global access, 0 defers to individual branch perms.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'tag_create' => array( + 'type' => 'int', + 'description' => 'Grant user access to create tags in the repository.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'tag_update' => array( + 'type' => 'int', + 'description' => 'Grant user access to update/modify any tag in the repository. 1 is global access, 0 defers to individual tag perms.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'tag_delete' => array( + 'type' => 'int', + 'description' => 'Grant user access to delete tags in the repository. 0 is no access, 1 is some access, 2 can delete all tags.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('repo_id', 'uid'), + ); + $schema['versioncontrol_auth_account_label'] = array( + 'description' => '', + 'fields' => array( + 'uid' => array( + 'description' => 'Foreign key to {users}.uid; uniquely identifies a Drupal user to whom this ACL data applies.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'repo_id' => array( + 'description' => 'Foreign key to {versioncontrol_repositories}.repo_id; identifies the repository to which this ACL data applies.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'label_id' => array( + 'description' => 'Foreign key to {versioncontrol_labels}.label_id; identifies the label (branch or tag) to which this ACL data applies.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'label_update' => array( + 'type' => 'int', + 'description' => 'Grant user access to update/modify this label.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'label_delete' => array( + 'type' => 'int', + 'description' => 'Grant user access to delete this label.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('repo_id', 'uid', 'label_id'), + 'indexes' => array( + 'label_id' => array('label_id'), + ), + ); + return $schema; } @@ -857,3 +966,128 @@ function versioncontrol_update_6308() { db_add_field($ret, 'versioncontrol_repositories', 'plugins', $plugins); return $ret; } + +/** + * Remove the defunct versioncontrol_accounts table and replace it with ones + * driven by auth plugins, specifically the + * VersioncontrolAuthHandlerMappedAccounts family of plugins. + */ +function versioncontrol_update_6309() { + $ret = array(); + + $account_table = array( + 'description' => 'ACL table, used by the VersioncontrolAuthHandlerMappedAccounts family of plugins, that stores ACL data on a per-uid/per-repo basis.', + 'fields' => array( + 'uid' => array( + 'description' => 'Foreign key to {users}.uid; uniquely identifies a Drupal user to whom this ACL data applies.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'repo_id' => array( + 'description' => 'Foreign key to {versioncontrol_repositories}.repo_id; identifies the repository to which this ACL data applies.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'access' => array( + 'type' => 'int', + 'description' => 'Base, global access to the repository. 0 indicates no access (disabled/inactive account; acts as a global deny), 1 indicates some level of access, 2 indicates global access (overrides granular access).', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'branch_create' => array( + 'type' => 'int', + 'description' => 'Grant user access to create branches in the repository.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'branch_update' => array( + 'type' => 'int', + 'description' => 'Grant user access to update/write to any branch in the repository. 1 is global access, 0 defers to individual branch perms.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'branch_delete' => array( + 'type' => 'int', + 'description' => 'Grant user access to delete any branch in the repository. 1 is global access, 0 defers to individual branch perms.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'tag_create' => array( + 'type' => 'int', + 'description' => 'Grant user access to create tags in the repository.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'tag_update' => array( + 'type' => 'int', + 'description' => 'Grant user access to update/modify any tag in the repository. 1 is global access, 0 defers to individual tag perms.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'tag_delete' => array( + 'type' => 'int', + 'description' => 'Grant user access to delete tags in the repository. 0 is no access, 1 is some access, 2 can delete all tags.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('repo_id', 'uid'), + ); + + db_create_table($ret, 'versioncontrol_auth_account', $account_table); + + $auth_label_table = array( + 'description' => '', + 'fields' => array( + 'uid' => array( + 'description' => 'Foreign key to {users}.uid; uniquely identifies a Drupal user to whom this ACL data applies.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'repo_id' => array( + 'description' => 'Foreign key to {versioncontrol_repositories}.repo_id; identifies the repository to which this ACL data applies.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'label_id' => array( + 'description' => 'Foreign key to {versioncontrol_labels}.label_id; identifies the label (branch or tag) to which this ACL data applies.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'label_update' => array( + 'type' => 'int', + 'description' => 'Grant user access to update/modify this label.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'label_delete' => array( + 'type' => 'int', + 'description' => 'Grant user access to delete this label.', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('repo_id', 'uid', 'label_id'), + 'indexes' => array( + 'label_id' => array('label_id'), + ), + ); + + db_create_table($ret, 'versioncontrol_auth_account_label', $auth_label_table); + + return $ret; +}