'Git access', 'page callback' => 'drupalorg_git_gateway_user_page', 'page arguments' => array(1), 'access callback' => 'user_edit_access', 'access arguments' => array(1), 'load arguments' => array('%map', '%index'), 'weight' => 1, 'type' => MENU_LOCAL_TASK, ); $items['admin/settings/drupalorg-git'] = array( 'title' => 'Drupal.org Git gateway', 'page callback' => 'drupal_get_form', 'page arguments' => array('drupalorg_git_gateway_defaults'), 'access arguments' => array('administer version control systems'), 'description' => 'Update Git e-mail text or globally disable push access.', 'type' => MENU_NORMAL_ITEM, ); $items['admin/settings/drupalorg-git/defaults'] = array( 'title' => 'Defaults', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => - 10, ); $items['admin/settings/drupalorg-git/gatectl'] = array( 'title' => 'Global Push Control', 'page arguments' => array('drupalorg_git_gateway_gatectl'), 'access arguments' => array('administer version control systems'), 'type' => MENU_LOCAL_TASK, ); return $items; } /** * Implementation of hook_user(). */ function drupalorg_git_gateway_user($op, &$edit, &$account, $category = NULL) { // An ugly shim to ensure that things show up nicely in the sub-tabs generated // by the User module. if ($op == 'categories') { return array( 'git' => array( 'name' => 'git', 'title' => t('Git access'), 'weight' => 1, // use defaults for access callback and arguments ), ); } else if ($op == 'after_update') { // Determine appropriate role grants for the account. By doing this in // after_update, we don't have to swim upstream against the rest of the user // system. // // Start by assuming we should not grant the perm. $grant = 0; // 0x01 == grant basic; 0x02 == grant vetted if (empty($account->git_disabled)) { // Grant the perm IFF consent has been given and a username has been set. $grant |= (!empty($account->git_username) && !empty($account->git_consent)) ? 0x01 : 0; } // Vetted state persists through disabling. $grant |= !empty($account->git_vetted) ? 0x02 : 0; $del_rids = array(); if ($grant & 0x01) { $vals = array( 'uid' => $account->uid, 'rid' => DRUPALORG_GIT_GATEWAY_RID, ); db_merge('users_roles')->key($vals)->fields($vals)->execute(); $account->roles[DRUPALORG_GIT_GATEWAY_RID] = 'Git user'; } else { unset($account->roles[DRUPALORG_GIT_GATEWAY_RID]); $del_rids[] = DRUPALORG_GIT_GATEWAY_RID; } if ($grant & 0x02) { $vals = array( 'uid' => $account->uid, 'rid' => DRUPALORG_GIT_GATEWAY_VETTED_RID, ); db_merge('users_roles')->key($vals)->fields($vals)->execute(); $account->roles[DRUPALORG_GIT_GATEWAY_VETTED_RID] = 'Git vetted user'; } else { unset($account->roles[DRUPALORG_GIT_GATEWAY_VETTED_RID]); $del_rids[] = DRUPALORG_GIT_GATEWAY_VETTED_RID; } if (0x03 & ~$grant) { // If both perms shouldn't be available, run a delete to be safe. $del = db_delete('users_roles') ->condition('uid', $account->uid) ->condition('rid', $del_rids) ->execute(); } } } /** * Implements hook_form_alter(). * * Modify the base user account editing form to disable the Git user and Git * vetted user roles from being modified directly. */ function drupalorg_git_gateway_form_alter(&$form, &$form_state, $form_id) { if ($form_id != 'user_profile_form' || $form['_category']['#value'] != 'account') { return; } // Disable the 'Git user' and 'Git vetted user' roles in the same way the // authenticated user role is disabled. if (user_access('administer permissions')) { $options =& $form['account']['roles']['#options']; foreach (array(DRUPALORG_GIT_GATEWAY_RID, DRUPALORG_GIT_GATEWAY_VETTED_RID) as $rid) { $checkbox = array( '#type' => 'checkbox', '#title' => $options[$rid], '#default_value' => in_array($rid, $form['account']['roles']['#default_value']), '#disabled' => TRUE, ); $form['account']['roles'][$rid] = $checkbox; // Remove the role from the original set of checkboxes. unset($options[$rid]); } $desc = t('The "Git user" and "Git vetted user" roles cannot be changed directly here. Manage them on the !link tab.', array('!link' => l('Git access', 'user/' . $form['_account']['#value']->uid . '/edit/git'))); $form['account']['roles'][DRUPALORG_GIT_GATEWAY_VETTED_RID]['#description'] = $desc; } } /** * Implementation of hook_help(). */ function drupalorg_git_gateway_help($path, $arg) { global $user; switch ($path) { case 'node/add': if (!empty($user->uid) && empty($user->git_consent) && !empty($user->git_vetted)) { return drupalorg_git_gateway_no_consent_error($user); } break; case 'project/user': if (!empty($user->uid) && empty($user->git_consent)) { // Vetted users know what's up, they just need to be reminded to check // the 'Git access' checkbox. if (!empty($user->git_vetted)) { return drupalorg_git_gateway_no_consent_error($user); } // Non-vetted users should be pointed to the documentation on projects. else { return t('To learn about projects, see Contributing code on Drupal.org.'); } } break; } } /** * Print out a message to users that have not consented to using Git. * * Includes a link to the 'Git access' tab on their account profile. * * @param $account * The fully-loaded user object of the account to print the message for. * * @return string * The text to display to users who have not consented to using Git. */ function drupalorg_git_gateway_no_consent_error($account) { $git_access_url = url('user/' . $account->uid . '/edit/git', array('query' => drupal_get_destination())); return t('In order to create projects, agree to the terms of service on Git access.', array('@git_access' => $git_access_url)); } function drupalorg_git_gateway_user_page($account) { require_once drupal_get_path('module', 'drupalorg_git_gateway') . "/drupalorg_git_gateway.user-form.inc"; return _drupalorg_git_gateway_user_page($account); } function drupalorg_git_gateway_defaults(&$form_state) { $form = array(); $form['project_git_gateway_disabled_msg'] = array( '#type' => 'textarea', '#title' => t('Default Git access disabled e-mail'), '#default_value' => variable_get('project_git_gateway_disabled_msg', DRUPALORG_GIT_DISABLE_MSG), '#description' => t('The default message to send to a user when disabling their Git push access.'), '#rows' => 10, ); return system_settings_form($form); } function drupalorg_git_gateway_gatectl(&$form_state) { $form = array(); $push_options = array( DRUPALORG_GIT_GATECTL_CORE => 'Disable pushes to core', DRUPALORG_GIT_GATECTL_PROJECTS => 'Disable pushes to all full projects', DRUPALORG_GIT_GATECTL_SANDBOXES => 'Disable pushes to all sandbox projects', ); $pushctl = (int) variable_get('drupalorg_git_gateway_pushctl', 0); $push_defaults = array(); if ($pushctl & DRUPALORG_GIT_GATECTL_CORE) { $push_defaults[] = DRUPALORG_GIT_GATECTL_CORE; } if ($pushctl & DRUPALORG_GIT_GATECTL_PROJECTS) { $push_defaults[] = DRUPALORG_GIT_GATECTL_PROJECTS; } if ($pushctl & DRUPALORG_GIT_GATECTL_SANDBOXES) { $push_defaults[] = DRUPALORG_GIT_GATECTL_SANDBOXES; } $form['pushctl'] = array( '#type' => 'checkboxes', '#title' => t('Git push global control'), '#options' => $push_options, '#default_value' => $push_defaults, '#description' => t('Toggling these checkboxes will immediately disable push access to the respective set of repositories. This is a hugely powerful setting, do not muck about with it!'), ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Save'), ); return $form; } function drupalorg_git_gateway_gatectl_submit($form, &$form_state) { $pushctl = 0; foreach(array_filter($form_state['values']['pushctl']) as $val) { $pushctl |= $val; } $old_pushctl = (int) variable_get('drupalorg_git_gateway_pushctl', 0); variable_set('drupalorg_git_gateway_pushctl', $pushctl); // Invoke notification hook if old pushctl != new pushctl if ($pushctl != $old_pushctl) { module_invoke_all('drupalorg_pushctl_changed', $pushctl); } } /** * Return total number of approved Git accounts. */ function drupalorg_git_gateway_get_total_accounts() { return db_result(db_query('SELECT count(distinct(uid)) FROM {users_roles} WHERE rid IN (%d, %d)', DRUPALORG_GIT_GATEWAY_RID, DRUPALORG_GIT_GATEWAY_VETTED_RID)); } /** * Created a suggested username based on the provided name value, and ensuring * no conflicts with existing git usernames. * * @param string $name */ function drupalorg_git_gateway_suggest_git_username($name) { $base_suggestion = preg_replace('/[^A-Za-z0-9\._-]/', '', $name); $len = strlen($base_suggestion); $result = db_query("SELECT git_username FROM {users} WHERE git_username LIKE ('%s%')", $name); $matches = array(); while ($row = db_fetch_object($result)) { $matches[] = $row->git_username; } // Ensure no duplicates, and add an incrementing counter if one is found. $i = 0; while (array_search($base_suggestion, $matches) !== FALSE) { $base_suggestion = substr($base_suggestion, 0, $len) . ++$i; } return $base_suggestion; } /** * Implements hook_versioncontrol_project_auth_data_alter(). * * Add more auth data to the array being returned via drush for the auth * service, in particular the 'global' state of the account. Also ensure that * we're using the git username, not the vanilla username. * * @param $data * @param $repository */ function drupalorg_git_gateway_versioncontrol_project_auth_data_alter(&$data, $repository, $project) { // Stick in the repo set val so the daemon knows how to deal with pushctl settings. if ($project->nid == DRUPALORG_CORE_NID) { $data['repo_group'] = DRUPALORG_GIT_GATECTL_CORE; } else { $data['repo_group'] = empty($project->project['sandbox']) ? DRUPALORG_GIT_GATECTL_PROJECTS : DRUPALORG_GIT_GATECTL_SANDBOXES; } // Attach project published/unpublished information. $data['status'] = (int) $project->status; $select = db_select('project_release_nodes', 'prn')->fields('prn', array('tag', 'rebuild')); $vpp_alias = $select->addJoin('INNER', 'versioncontrol_project_projects', 'vpp', 'prn.pid = vpp.nid'); $result = $select->condition($vpp_alias . '.repo_id', $repository->repo_id) ->execute()->fetchAllKeyed(); $tags = $branches = array(); foreach ($result as $label_name => $branch) { if (empty($branch)) { $tags[] = $label_name; } else { $branches[] = $label_name; } } $data['protected_labels'] = array( 'tags' => $tags, 'branches' => $branches, ); $result = db_select('users', 'u') ->fields('u', array('name', 'git_username')) ->condition('u.name', array_keys($data['users']), 'IN') ->execute()->fetchAllKeyed(); foreach ($result as $name => $git_username) { $account = user_load($data['users'][$name]['uid']); if ($name != $git_username) { $data['users'][$git_username] = $data['users'][$name]; unset($data['users'][$name]); } $ap =& $data['users'][$git_username]; $ap['global'] = 0; // User has global access disabled for some reason. if (!isset($account->roles[DRUPALORG_GIT_GATEWAY_RID]) || empty($account->status)) { $ap['global'] |= DRUPALORG_GIT_AUTH_NO_ROLE; if (!empty($account->git_disabled)) { $ap['global'] |= DRUPALORG_GIT_AUTH_ACCOUNT_SUSPENDED; } if (empty($account->git_consent)) { $ap['global'] |= DRUPALORG_GIT_AUTH_NOT_CONSENTED; } if (empty($account->status)) { $ap['global'] |= DRUPALORG_GIT_AUTH_ACCOUNT_BLOCKED; } } } } /** * Log an incoming push event to our special logging tables. * * This is all a stopgap until we have a real activity stream system. * * @param array $data * An array of push data, originating from the post-receive hook. */ function drupalorg_git_gateway_versioncontrol_git_raw_push_data($data) { $push_id = db_insert('drupalorg_git_push_log')->fields(array( 'uid' => $data['uid'], 'timestamp' => $data['timestamp'], 'repo_id' => $data['repo_id'], ))->execute(); // Create a new insert object for multi-insert fun. $insert = db_insert('drupalorg_git_push_log_refs') ->fields(array('push_id', 'refname', 'reftype', 'old_obj', 'new_obj')); $all_refdata = explode("\n", $data['data']); foreach ($all_refdata as $refdata) { if (empty($refdata)) { continue; // last element is usually empty, skip it } list($start, $end, $refpath) = explode(' ', $refdata); $_refpath = explode('/', $refpath); $ref = array_pop($_refpath); $insert->values(array( 'push_id' => $push_id, 'refname' => $ref, 'reftype' => $_refpath[1] == 'tags' ? 2 : 1, 'old_obj' => $start, 'new_obj' => $end, )); } $insert->execute(); } /** * Implements hook_mail() * * @param $key * @param $message */ function drupalorg_git_gateway_mail($key, &$message, $params = array()) { $language = $message['language']; if ($key == 'disabled') { $message['subject'] = t('Git push access disabled'); $message['body'] = $params['body']; return; } }