~bzr-pqm/bzr/bzr.dev

3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
1
#!/usr/bin/env python
2
###############################################################################
3
#
4
#  bzr_access:
5
#    Simple access control for shared bazaar repository accessed over ssh.
6
#
3112.1.3 by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style.
7
# Copyright (C) 2007 Balint Aradi
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
8
#
3099.4.6 by John Arbash Meinel
Add GPL license to bzr_access script
9
# This program is free software; you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation; either version 2 of the License, or
12
# (at your option) any later version.
13
#
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
# GNU General Public License for more details.
18
#
19
# You should have received a copy of the GNU General Public License
20
# along with this program; if not, write to the Free Software
21
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
#
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
23
###############################################################################
3099.4.4 by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST
24
"""
25
Invocation: bzr_access <bzr_executable> <repo_collection> <user>
26
27
The script extracts from the SSH_ORIGINAL_COMMAND environment variable the
28
repository, which bazaar tries to access through the bzr+ssh protocol. The
29
repository is assumed to be relative to <repo_collection>. Based
30
on the configuration file <repo_collection>/bzr_access.conf it determines
31
the access rights (denied, read-only, read-write) for the specified user.
32
If the user has read-only or read-write access a bazaar smart server is
33
started for it in read-only or in read-write mode, rsp., using the specified
34
bzr executable.
35
36
Config file: INI format, pretty much similar to the authfile of subversion.
37
38
Groups can be defined in the [groups] section. The options in this block are
39
the names of the groups to be defined, the corresponding values the lists of
40
the users belonging to the given groups. (User names must be separated by
3112.1.4 by Balint Aradi
Changing example to show comma separated user names.
41
commas.)
3099.4.4 by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST
42
3475.1.1 by j at oil21
fix contrib/bzr_access
43
Right now only one section is supported [/], defining the permissions for the
44
repository. The options in those sections are user names or group references
45
(group name with a leading '@'), the corresponding values are the 
46
permissions: 'rw', 'r' and '' (without the quotes)
47
for read-write, read-only and no access, respectively.
3099.4.4 by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST
48
49
Sample bzr_access.conf::
50
51
   [groups]
52
   admins = alpha
3112.1.4 by Balint Aradi
Changing example to show comma separated user names.
53
   devels = beta, gamma, delta
3099.4.4 by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST
54
   
3475.1.1 by j at oil21
fix contrib/bzr_access
55
   [/]
3099.4.4 by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST
56
   @admins = rw
57
   @devels = r
58
3099.4.5 by John Arbash Meinel
A bit more documentation cleanup
59
This allows you to set up a single SSH user, and customize the access based on
60
ssh key. Your ``.ssh/authorized_key`` file should look something like this::
3099.4.4 by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST
61
3099.4.5 by John Arbash Meinel
A bit more documentation cleanup
62
   command="/path/to/bzr_access /path/to/bzr /path/to/repository <username>",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-<type> <key>
3099.4.4 by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST
63
"""
64
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
65
import ConfigParser
3099.4.2 by John Arbash Meinel
Some simple spelling fixes, and switch to using subprocess since that is space-in-filename safe
66
import os
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
67
import re
3099.4.2 by John Arbash Meinel
Some simple spelling fixes, and switch to using subprocess since that is space-in-filename safe
68
import subprocess
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
69
import sys
70
71
CONFIG_FILE = "bzr_access.conf"
72
SCRIPT_NAME = os.path.basename(sys.argv[0])
73
74
# Permission constants
75
PERM_DENIED = 0
76
PERM_READ = 1
77
PERM_READWRITE = 2
78
PERM_DICT = { "r": PERM_READ, "rw": PERM_READWRITE }
79
80
# Exit codes
81
EXIT_BAD_NR_ARG = 1
82
EXIT_BZR_NOEXEC = 2
83
EXIT_REPO_NOREAD = 3
84
EXIT_BADENV = 4
85
EXIT_BADDIR = 5
86
EXIT_NOCONF = 6
87
EXIT_NOACCESS = 7
3112.1.3 by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style.
88
EXIT_BADUSERNAME = 8
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
89
90
# pattern for the bzr command passed to ssh
91
PAT_SSH_COMMAND = re.compile(r"""^bzr\s+
92
                             serve\s+
93
                             --inet\s+
94
                             --directory=(?P<dir>\S+)\s+
95
                             --allow-writes\s*$""", re.VERBOSE)
96
3099.4.4 by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST
97
# Command line for starting bzr
3099.4.2 by John Arbash Meinel
Some simple spelling fixes, and switch to using subprocess since that is space-in-filename safe
98
BZR_OPTIONS = ['serve', '--inet', '--directory']
99
BZR_READWRITE_FLAGS = ['--allow-writes']
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
100
101
102
3112.1.3 by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style.
103
def error(msg, exit_code):
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
104
    """Prints error message to stdout and exits with given error code."""
105
    
106
    print >>sys.stderr, "%s::error: %s" % (SCRIPT_NAME, msg)
3112.1.3 by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style.
107
    sys.exit(exit_code)
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
108
  
109
110
111
class AccessManager(object):
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
112
    """Manages the permissions, can be queried for a specific user and path."""
113
    
114
    def __init__(self, fp):
115
        """:param fp: File like object, containing the configuration options.
116
        """
3099.4.7 by John Arbash Meinel
Add a comment about using configobj instead of ConfigParser
117
        # TODO: jam 20071211 Consider switching to bzrlib.util.configobj
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
118
        self.config = ConfigParser.ConfigParser()
119
        self.config.readfp(fp)
120
        self.groups = {}
121
        if self.config.has_section("groups"):
122
            for group, users in self.config.items("groups"):
3112.1.3 by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style.
123
                self.groups[group] = set([ s.strip() for s in users.split(",")])
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
124
        
125
3475.1.1 by j at oil21
fix contrib/bzr_access
126
    def permission(self, user):
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
127
        """Determines the permission for a given user and a given path
128
        :param user: user to look for.
129
        :return: permission.
130
        """
3475.1.1 by j at oil21
fix contrib/bzr_access
131
        configSection = "/"
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
132
        perm = PERM_DENIED
3475.1.1 by j at oil21
fix contrib/bzr_access
133
        pathFound = self.config.has_section(configSection)
134
        if (pathFound):
135
            options = reversed(self.config.options(configSection))
136
            for option in options:
137
                value = PERM_DICT.get(self.config.get(configSection, option),
138
                                      PERM_DENIED)
139
                if self._is_relevant(option, user):
140
                    perm = value
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
141
        return perm
3475.1.1 by j at oil21
fix contrib/bzr_access
142
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
143
      
3112.1.3 by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style.
144
    def _is_relevant(self, option, user):
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
145
        """Decides if a certain option is relevant for a given user.
146
      
3112.1.3 by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style.
147
        An option is relevant if it is identical with the user or with a
148
        reference to a group including the user.
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
149
      
150
        :param option: Option to check.
151
        :param user: User
152
        :return: True if option is relevant for the user, False otherwise.
153
        """
154
        if option.startswith("@"):
3112.1.3 by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style.
155
            result = (user in self.groups.get(option[1:], set()))
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
156
        else:
157
            result = (option == user)
158
        return result
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
159
160
161
3112.1.3 by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style.
162
def get_directory(command):
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
163
    """Extracts the directory name from the command pass to ssh.
164
    :param command: command to parse.
165
    :return: Directory name or empty string, if directory was not found or if it
166
    does not start with '/'.
167
    """
168
    match = PAT_SSH_COMMAND.match(command)
169
    if not match:
170
        return ""
171
    directory = match.group("dir")
172
    return os.path.normpath(directory)
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
173
174
175
176
############################################################################
177
# Main program
178
############################################################################
179
def main():
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
180
    # Read arguments
181
    if len(sys.argv) != 4:
182
        error("Invalid number or arguments.", EXIT_BAD_NR_ARG)
183
    (bzrExec, repoRoot, user) = sys.argv[1:4]
184
    
185
    # Sanity checks
186
    if not os.access(bzrExec, os.X_OK):
187
        error("bzr is not executable.", EXIT_BZR_NOEXEC)
188
    if not os.access(repoRoot, os.R_OK):
189
        error("Path to repository not readable.", EXIT_REPO_NOREAD)
190
    
191
    # Extract the repository path from the command passed to ssh.
192
    if not os.environ.has_key("SSH_ORIGINAL_COMMAND"):
193
        error("Environment variable SSH_ORIGINAL_COMMAND missing.", EXIT_BADENV)
3112.1.3 by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style.
194
    directory = get_directory(os.environ["SSH_ORIGINAL_COMMAND"])
3112.1.5 by John Arbash Meinel
a small bit of cleanup.
195
    if len(directory) == 0:
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
196
        error("Bad directory name.", EXIT_BADDIR)
3112.1.3 by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style.
197
198
    # Control user name
199
    if not user.isalnum():
200
        error("Invalid user name", EXIT_BADUSERNAME)
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
201
    
202
    # Read in config file.
203
    try:
204
        fp = open(os.path.join(repoRoot, CONFIG_FILE), "r")
205
        try:
206
            accessMan = AccessManager(fp)
207
        finally:
208
            fp.close()
209
    except IOError:
210
        error("Can't read config file.", EXIT_NOCONF)
211
    
212
    # Determine permission and execute bzr with appropriate options
3475.1.1 by j at oil21
fix contrib/bzr_access
213
    perm = accessMan.permission(user)
214
    command = [bzrExec] + BZR_OPTIONS + [repoRoot]
3099.4.3 by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines.
215
    if perm == PERM_READ:
216
        # Nothing extra needed for readonly operations
217
        pass
218
    elif perm == PERM_READWRITE:
219
        # Add the write flags
220
        command.extend(BZR_READWRITE_FLAGS)
221
    else:
222
        error("Access denied.", EXIT_NOACCESS)
223
    return subprocess.call(command)
3099.4.1 by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh
224
225
226
if __name__ == "__main__":
227
  main()
228
229
230
### Local Variables:
231
### mode:python
232
### End: