hgserver: print LDAP group membership for no-op login (bug 1372303); r?fubar draft
authorGregory Szorc <gps@mozilla.com>
Wed, 14 Jun 2017 17:58:09 -0700
changeset 11208 8a5ca9a7ad91a0d0517ce6516616d6c5a05a3b5d
parent 11206 453bcf1bd611064f8acebfb7f16f94d1942c2c09
push id1706
push userbmo:gps@mozilla.com
push dateThu, 15 Jun 2017 00:58:13 +0000
reviewersfubar
bugs1372303
hgserver: print LDAP group membership for no-op login (bug 1372303); r?fubar This seems like a useful thing to have to help users self-debug permissions failures. MozReview-Commit-ID: IMiRGiIowwV
docs/hgmozilla/auth.rst
hgserver/pash/hg_helper.py
hgserver/pash/ldap_helper.py
hgserver/tests/test-auth.t
--- a/docs/hgmozilla/auth.rst
+++ b/docs/hgmozilla/auth.rst
@@ -68,16 +68,29 @@ Verify your SSH settings are working by 
 Your terminal output should resemble the following::
 
    $ ssh hg.mozilla.org
    A SSH connection has been successfully established.
 
    Your account (me@example.com) has privileges to access Mercurial over
    SSH.
 
+  You are a member of the following LDAP groups that govern source control
+  access:
+
+     scm_level_1
+
+  This will give you write access to the following repos:
+
+     Try
+
+  You will NOT have write access to the following repos:
+
+     Autoland (integration/autoland), Firefox Repos (mozilla-central, releases/*), ...
+
    You did not specify a command to run on the server. This server only
    supports running specific commands. Since there is nothing to do, you
    are being disconnected.
    Connection to hg.mozilla.org closed.
 
 Authenticating with Services
 ============================
 
--- a/hgserver/pash/hg_helper.py
+++ b/hgserver/pash/hg_helper.py
@@ -9,16 +9,17 @@ import os
 import sys
 import re
 import shlex
 import subprocess
 
 from ldap_helper import (
     get_ldap_attribute,
     get_ldap_settings,
+    get_scm_groups,
 )
 import repo_group
 from sh_helper import (
     prompt_user,
     run_command,
 )
 
 
@@ -43,16 +44,41 @@ are being disconnected.
 '''.lstrip()
 
 INVALID_SSH_COMMAND = '''
 The command you specified is not allowed on this server.
 
 Goodbye.
 '''.lstrip()
 
+LDAP_GROUP_MEMBERSHIP = """
+You are a member of the following LDAP groups that govern source control
+access:
+
+   {groups}
+
+This will give you write access to the following repos:
+
+   {access}
+
+You will NOT have write access to the following repos:
+
+   {no_access}
+""".lstrip()
+
+NO_LDAP_GROUP_MEMBERSHIP = """
+You are NOT a member of any LDAP groups that govern source control access.
+
+You will NOT be able to push to any repository until you have been granted
+commit access.
+
+See https://www.mozilla.org/about/governance/policies/commit/access-policy/ for
+more information.
+""".lstrip()
+
 USER_REPO_EXISTS = """
 You already have a repo called %s.
 
 If you think this is wrong, please file a Developer Services :: hg.mozilla.org
 bug at
 https://bugzilla.mozilla.org/enter_bug.cgi?product=Developer%%20Services&component=Mercurial%%3A%%20hg.mozilla.org
 """.strip()
 
@@ -118,16 +144,64 @@ def is_valid_user(mail):
     if account_status == 'TRUE':
         return 1
     elif account_status == 'FALSE':
         return 2
     else:
         return 0
 
 
+GROUP_REPOS = {
+    'scm_level_1': {
+        'Try',
+        'User Repos (users/)',
+    },
+    'scm_level_2': {
+        'Project Repos (projects/)',
+    },
+    'scm_level_3': {
+        'Firefox Repos (mozilla-central, releases/*)',
+    },
+    'scm_autoland': {
+        'Autoland (integration/autoland)',
+    },
+    'scm_l10n': {
+        'Localization Repos (releases/l10n/*, others)',
+    }
+}
+
+
+def group_membership_message(mail):
+    """Obtain a message denoting LDAP group membership."""
+    groups = get_scm_groups(mail)
+
+    if groups is None:
+        return 'Unable to determine LDAP group membership.'
+    elif not groups:
+        return NO_LDAP_GROUP_MEMBERSHIP
+    else:
+        access = set()
+        for group in groups:
+            access |= GROUP_REPOS.get(group, set())
+
+        no_access = set()
+        for group, values in GROUP_REPOS.items():
+            if group not in groups:
+                no_access |= values
+
+        if not access:
+            access.add('Unknown')
+        if not no_access:
+            no_access.add('Unknown')
+
+        return LDAP_GROUP_MEMBERSHIP.format(
+            groups=', '.join(sorted(groups)),
+            access=', '.join(sorted(access)),
+            no_access=', '.join(sorted(no_access)))
+
 # Please be very careful when you relax/change the regular expressions.
 # Being lax can open us up to all kind of security problems.
 def is_valid_repo_name(repo_name):
     # Trailing slashes can be ignored.
     repo_name = repo_name.rstrip('/')
 
     part_re = re.compile(
         r'^[a-zA-Z0-9]'       # must start with a letter or number
@@ -580,16 +654,18 @@ def mozreview_ldap_associate(args):
 
 
 def serve(cname, enable_repo_config=False, enable_repo_group=False,
           enable_user_repos=False,
           enable_mozreview_ldap_associate=False):
     ssh_command = os.getenv('SSH_ORIGINAL_COMMAND')
     if not ssh_command:
         sys.stderr.write(SUCCESSFUL_AUTH % os.environ['USER'])
+        sys.stderr.write(group_membership_message(os.environ['USER']))
+        sys.stderr.write('\n')
         sys.stderr.write(NO_SSH_COMMAND)
         sys.exit(1)
 
     args = shlex.split(ssh_command)
 
     if args[0] == 'hg':
         # SECURITY it is critical that invoked commands be limited to
         # `hg -R <path> serve --stdio`. If a user manages to pass arguments
--- a/hgserver/pash/ldap_helper.py
+++ b/hgserver/pash/ldap_helper.py
@@ -86,8 +86,28 @@ def update_access_date(mail, attr, value
                                                  '%Y%m%d%H%M%SZ')
     # Attribute not yet set.
     except KeyError:
         # Default to something very old. ~20 years.
         last_access = now - datetime.timedelta(days=7300)
 
     if last_access < yesterday:
         ldap_conn_write.modify_s(dn, [(ldap.MOD_REPLACE, attr, value)])
+
+
+def get_scm_groups(mail):
+    """Obtain SCM LDAP group membership for a specified user."""
+    settings = get_ldap_settings()
+    conn = ldap_connect(settings['url'])
+    if not conn:
+        return None
+
+    fltr = '(&(cn=scm_*)(memberUid=%s))' % mail
+
+    result = conn.search_s('ou=groups,dc=mozilla', ldap.SCOPE_ONELEVEL,
+                           fltr, ['cn'])
+
+    groups = set()
+    for dn, attrs in result:
+        for group in attrs['cn']:
+            groups.add(group)
+
+    return groups
--- a/hgserver/tests/test-auth.t
+++ b/hgserver/tests/test-auth.t
@@ -51,25 +51,34 @@ SSH as a valid user without proper key
   
   # numResponses: 2
   # numEntries: 1
 
   $ ssh -T -F ssh_config -i key1 -l user1@example.com -p $HGPORT $SSH_SERVER
   Permission denied (publickey).\r (esc)
   [255]
 
-SSH with a valid key gives us warning about no command
+SSH with a valid key gives us warning about no command. Also prints note about
+lack of LDAP group membership.
 
   $ hgmo add-ssh-key user1@example.com - < key1.pub
   $ ssh -T -F ssh_config -i key1 -l user1@example.com -p $HGPORT $SSH_SERVER
   A SSH connection has been successfully established.
   
   Your account (user1@example.com) has privileges to access Mercurial over
   SSH.
   
+  You are NOT a member of any LDAP groups that govern source control access.
+  
+  You will NOT be able to push to any repository until you have been granted
+  commit access.
+  
+  See https://www.mozilla.org/about/governance/policies/commit/access-policy/ for
+  more information.
+  
   You did not specify a command to run on the server. This server only
   supports running specific commands. Since there is nothing to do, you
   are being disconnected.
   [1]
 
 SSH with invalid command prints appropriate error message
 
   $ ssh -T -F ssh_config -i key1 -l user1@example.com -p $HGPORT $SSH_SERVER foobar
@@ -78,16 +87,46 @@ SSH with invalid command prints appropri
   Your account (user1@example.com) has privileges to access Mercurial over
   SSH.
   
   The command you specified is not allowed on this server.
   
   Goodbye.
   [1]
 
+SCM LDAP group membership is printed with no-op login.
+
+  $ hgmo add-user-to-group user1@example.com scm_autoland
+  $ hgmo add-user-to-group user1@example.com scm_level_1
+  $ hgmo add-user-to-group user1@example.com scm_level_2
+
+  $ ssh -T -F ssh_config -i key1 -l user1@example.com -p $HGPORT $SSH_SERVER
+  A SSH connection has been successfully established.
+  
+  Your account (user1@example.com) has privileges to access Mercurial over
+  SSH.
+  
+  You are a member of the following LDAP groups that govern source control
+  access:
+  
+     scm_autoland, scm_level_1, scm_level_2
+  
+  This will give you write access to the following repos:
+  
+     Autoland (integration/autoland), Project Repos (projects/), Try, User Repos (users/)
+  
+  You will NOT have write access to the following repos:
+  
+     Firefox Repos (mozilla-central, releases/*), Localization Repos (releases/l10n/*, others)
+  
+  You did not specify a command to run on the server. This server only
+  supports running specific commands. Since there is nothing to do, you
+  are being disconnected.
+  [1]
+
 Successful login should set hgAccessDate LDAP attribute
 
   $ hgmo exec hgssh /usr/bin/ldapsearch -b 'dc=mozilla' -s sub -x mail=user1@example.com
   # extended LDIF
   #
   # LDAPv3
   # base <dc=mozilla> with scope subtree
   # filter: mail=user1@example.com
@@ -191,16 +230,29 @@ No HG access prints helpful error messag
 Do another login to verify no pash errors are present
 
   $ ssh -T -F ssh_config -i key1 -l user1@example.com -p $HGPORT $SSH_SERVER
   A SSH connection has been successfully established.
   
   Your account (user1@example.com) has privileges to access Mercurial over
   SSH.
   
+  You are a member of the following LDAP groups that govern source control
+  access:
+  
+     scm_autoland, scm_level_1, scm_level_2
+  
+  This will give you write access to the following repos:
+  
+     Autoland (integration/autoland), Project Repos (projects/), Try, User Repos (users/)
+  
+  You will NOT have write access to the following repos:
+  
+     Firefox Repos (mozilla-central, releases/*), Localization Repos (releases/l10n/*, others)
+  
   You did not specify a command to run on the server. This server only
   supports running specific commands. Since there is nothing to do, you
   are being disconnected.
   [1]
 
   $ hgmo exec hgssh cat /var/log/pash.log
 
 hgAccountEnabled=FALSE shows account disabled message
@@ -290,16 +342,29 @@ Failure to connect to LDAP master server
 
   $ ssh -T -F ssh_config -i key1 -l user1@example.com -p $HGPORT $SSH_SERVER
   Could not connect to the LDAP server at ldap://localhost:6000
   A SSH connection has been successfully established.
   
   Your account (user1@example.com) has privileges to access Mercurial over
   SSH.
   
+  You are a member of the following LDAP groups that govern source control
+  access:
+  
+     scm_autoland, scm_level_1, scm_level_2
+  
+  This will give you write access to the following repos:
+  
+     Autoland (integration/autoland), Project Repos (projects/), Try, User Repos (users/)
+  
+  You will NOT have write access to the following repos:
+  
+     Firefox Repos (mozilla-central, releases/*), Localization Repos (releases/l10n/*, others)
+  
   You did not specify a command to run on the server. This server only
   supports running specific commands. Since there is nothing to do, you
   are being disconnected.
   [1]
 
 Can pull when LDAP master is not available
 
   $ hgmo create-repo mozilla-central scm_level_3