1 ___doc__ = """
2
3 Authentication methods and classes for SPyRO remote objects based on remote web authenticators.
4
5 """
6
7 import os
8 import sys
9 import md5
10 import base64
11 import time
12 import threading
13 import marshal
14 import random
15 import SPyRO
16 from SPyRO import policies
17 AuthError = policies.AuthError
18 import warn
19 import types
20
21 warn = warn.Warn("auth3k.authweb")
22
24 key = map(ord, x)
25 data = map(ord, y)
26 l = len(key)
27 res = []
28 for i,x in enumerate(data):
29 data[i] = chr(x ^ key[i % l])
30 return "".join(data)
31
32 try:
33 from Crypto.Cipher import XOR
35 return XOR.new(x).encrypt(y)
36 except ImportError:
37 warn.debug("Unable to use the Crypto package")
38 warn.debug("Using slower and insecure functions")
39 xorcrypt = myxorcrypt
40
42 """
43 An environment to authenticate objects
44
45 Objects of this class must describe Expirations, Autentication Server Strings,
46 Expiration Dates, Overload of users, Generators of String Authenticators,
47 Authentications Functions, etc.
48
49 """
50 - def __init__(self, seedfilename=None,expirationsoft=86400,expirationhard=864000,minseeds=100,maxseeds=200):
51 """
52 Properties
53
54 expirationsoft. The maximum time to use any authentication
55 expirationhard. The maximum time to create new authentication seeds
56 server_auth. Array of authenticity strings
57 last_auth. Last time of the server_auth
58 lock. Lock for concurrent access to some methods
59
60 If |seedfilename| is given then the random seeds are saved, and the next time
61 they will be remembered and used. If |seedfilename| is None, then volatile
62 seeds will be used.
63 |expirationsoft|,|expirationhard| The time (in seconds) before change auth seeds
64 (soft defaults to %(expirationsoft)s and hard defaults to %(expirationhard)s)
65 """%locals()
66 self.minseeds = minseeds
67 self.maxseeds = maxseeds
68 self.expirationsoft = expirationsoft
69 self.expirationhard = expirationhard
70 self.server_auth = None
71 self.last_auth = 0
72 self.last_mtime = 0
73 self.lock = threading.Lock()
74 self.seedfilename = seedfilename
75 self._update_global_auth()
76
78 if not self.seedfilename:
79 return
80 try:
81 mtime = os.stat(self.seedfilename).st_mtime
82 if self.last_mtime != mtime:
83 self.last_mtime = mtime
84 f = open(self.seedfilename,"r")
85 self.last_auth, self.server_auth = marshal.loads(f.read())
86 f.close()
87 except (OSError,IOError), e:
88 warn.debug("(Ignore this message if seedfilename doesn't exists yet) ", self.seedfilename, " ", e)
89
91 """ Creates a |size|-set of random strings to be used as authentication string generators """
92 self.last_auth = time.time()
93 self.server_auth = []
94 for x in xrange(size):
95 self.server_auth.append( md5.new(str((random.random(),time.time(),random.random()))).hexdigest() )
96 if self.seedfilename:
97 f = open(self.seedfilename,"w")
98 f.write(marshal.dumps((self.last_auth, self.server_auth)))
99 f.close()
100 warn.debug("Writing %s seeds"%len(self.server_auth))
101 try:
102 os.chmod(self.seedfilename, 0600)
103 except OSError, e:
104 warn.debug("Can't set permissions to 0600 to the seedfile ",repr(self.seedfilename), e)
105 warn.debug("Please fix this... Ignoring and running")
106
108 """ Change authorization codes """
109 self._read_global_auth()
110 ltime = time.time()
111 if (self.last_auth == 0) or ((self.last_auth + self.expirationhard) < ltime):
112 self.lock.acquire()
113 try:
114 self._random_auth(random.randint(self.minseeds,self.maxseeds))
115 finally:
116 self.lock.release()
117
119 """
120 Returns the list of users in |groupname|
121
122 This method must be overloaded to overload groups
123 """
124 try:
125 import grp
126 return grp.getgrnam(groupname).gr_mem
127 except ImportError:
128 warn.warn("Unable to load grp support, you must use another authentication method")
129
131 """ Autenticate against its system's password.
132 If you need to validate against any other source of passwords or validations
133 this is the right method to overload.
134 """
135 try:
136 import authpam
137 return authpam.authunix(user,password)
138 except ImportError:
139 warn.warn("Unable to load pam support, you must use other authentication method")
140
145
147 """
148 Creates an authentication string if the user and password are valid.
149 If an error happen, it returns an empty authentication string.
150 """
151 if not self.validate_user(user, password):
152 raise AuthError("The user %s doesn't match with the given password"%user)
153 return self._create_authentication(user)
154
156 self._update_global_auth()
157 now = str(int(time.time()))
158 ran = random.randint(0, len(self.server_auth) - 1)
159 dig = md5.new(now + user + self.server_auth[ran]).hexdigest()
160 xor = xorcrypt(self.server_auth[0],
161 "".join(['P',dig,str(ran),',',now]))
162 return base64.encodestring(xor).rstrip()
163
165 """ Checks and validates the authenticity of the |auth| string for |user|
166
167 It can update the global authentication strings
168 """
169 if not auth: return False
170 self._update_global_auth()
171 try:
172 authstring = base64.decodestring(auth+"\n")
173 authstring = xorcrypt(self.server_auth[0], authstring)
174 authtype,authdig,authtmp = authstring[0],authstring[1:33],authstring[33:]
175 authran,authnow = authtmp.split(",")
176 if md5.new(authnow + user + self.server_auth[int(authran)]).hexdigest() != authdig:
177 return False
178 except Exception, err:
179 warn.warn( 'Auth.authenticate Code 1:', err)
180 return False
181 except:
182 warn.log( 'Auth.authenticate Code 2:', err)
183 return False
184 if checksoft and (self.expirationsoft < (time.time() - int(authnow))):
185 return False
186 return True
187
188
189 DefaultAuthEnviron = None
190
192 """ A Simple wrapper to share / export the method create_authentication """
194 self.environ = environ
195
198
201
209
214
217
218 -class Auth(policies.AuthAllowCall):
220 """ Creates an Auth object
221 |environ| is a AuthEnviron describing the methods to perform authentications
222
223 Rules of permissions (self.perm):
224
225 self.perm is a dictionary (methodname => rule)
226 self.permobjects (Same but for an object level management)
227
228 Rules:
229 if self.perm[methodname] doesn't exists
230 The method needs a valid authentication, but not an special user
231
232 if self.perm[methodname] is True
233 The method is public and everyone is allowed to access it
234
235 if self.perm[methodname] is False
236 The method is private and cannot be accessed from SPyRO
237
238 if self.perm[methodname] is [ [list-of-valid-users], [list-of-valid-groups], [list-of-non-valid-users] ]
239 Restricts the execution of the method
240 """
241 self.perms = { }
242 self.permsobject = {}
243 if environ is None: environ = DefaultAuthEnviron
244 self.environ = environ
245
249
251 return self._rawauth(req['auth'], req['objname'], req['attrname'])
252
253 - def _rawauth(self, auth, objname, attrname):
254 try:
255 self._rawauth_(auth, objname, attrname)
256 except AuthGood, e:
257 warn.debug("Access granted as ", repr(auth), " to ", repr("%s.%s"%(objname,attrname)), ": ", e)
258 return True
259 except AuthBad, e:
260 warn.debug("Access denied as ", repr(auth), " to ", repr("%s.%s"%(objname,attrname)), ": ", e)
261 return False
262 except Exception, e:
263 warn.debug("Error in access denied as ", repr(auth), " to ", repr("%s.%s"%(objname,attrname)), ": ", e)
264 return False
265
266 - def _rawauth_(self, auth, objname, attrname):
267
268 if auth is None:
269 authuser = None
270 authstring = None
271 else:
272 try:
273 authuser, authstring = auth
274 except ValueError:
275 raise AuthBad, "Bad string authentication format %s"%repr(auth)
276 try:
277 permobj = self.permsobject[objname]
278 except:
279 permobj = None
280 if permobj == False:
281 raise AuthBad, "Object is private"
282 try:
283 perm = self.perms[attrname]
284 except KeyError:
285 perm = None
286 if perm == True:
287 raise AuthGood, "Method is public"
288 if perm == False:
289 raise AuthBad, "Method is private"
290 if not self.environ.authenticate(authuser, authstring):
291 raise AuthBad, "Authentication is not valid to this user"
292
293
294
295 if permobj and (authuser in permobj[2]):
296 raise AuthBad, "User denied at object level"
297
298 validuser = False
299 if perm:
300 if authuser in perm[2]:
301 raise AuthBad, "User denied in this method"
302 if authuser in perm[0]:
303 raise AuthGood, "User allowed"
304 for g in perm[1]:
305 try:
306 if authuser in self.environ.getgroup(g):
307 raise AuthGood, "User in a valid group for this method"
308 except KeyError:
309 warn.debug("_rawauth Group '", g,"' doesn't exist")
310 raise AuthBad, "User not allowed by group"
311 else:
312
313 if permobj is None:
314 raise AuthGood, "Allowed at object level, general auth"
315 else:
316 if authuser in permobj[0]: raise AuthGood, "User allowed at object level"
317 for g in permobj[1]:
318 try:
319 warn.debug(authuser, ":", self.environ.getgroup(g))
320 if authuser in self.environ.getgroup(g):
321 validuser = True
322 break
323 except KeyError:
324 warn.debug("_rawauth Group '", g ,"' doesn't exist")
325 if validuser:
326 raise AuthGood, "Allowed at object level"
327 else:
328 raise AuthBad, "User is not in a valid group for this object"
329