Package auth3k :: Module authweb
[hide private]
[frames] | no frames]

Source Code for Module auth3k.authweb

  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   
23 -def myxorcrypt(x,y):
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
34 - def xorcrypt(x,y):
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
41 -class AuthEnviron:
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
77 - def _read_global_auth(self):
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
90 - def _random_auth(self, size):
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
107 - def _update_global_auth(self):
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
118 - def getgroup(self, groupname):
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
130 - def validate_user(self, user, password):
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
141 - def update_authentication(self, user, auth):
142 if self.authenticate(user, auth, False): 143 return self._create_authentication(user) 144 return ""
145
146 - def create_authentication(self, user, password):
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
155 - def _create_authentication(self, user):
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
164 - def authenticate(self, user, auth, checksoft=True):
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 # The default environment 189 DefaultAuthEnviron = None 190
191 -class PublicAuthenticator:
192 """ A Simple wrapper to share / export the method create_authentication """
193 - def __init__(self, environ):
194 self.environ = environ
195
196 - def create_authentication(self, user, password):
197 return self.environ.create_authentication(user, password)
198
199 - def update_authentication(self, user, authstring):
200 return self.environ.update_authentication(user, authstring)
201
202 -def default_public_authenticator(environ=None):
203 """ Creates a public authenticator, useful to share with SPyRO """ 204 global DefaultAuthEnviron 205 if DefaultAuthEnviron is None: 206 DefaultAuthEnviron = AuthEnviron() 207 if environ is None: environ = DefaultAuthEnviron 208 return PublicAuthenticator(environ)
209
210 -def set_default_environ(environ):
211 """ Sets the default environment. The default environment is used if environ=None in the Auth constructor """ 212 global DefaultAuthEnviron 213 DefaultAuthEnviron = environ
214
215 -class AuthGood(Exception): pass
216 -class AuthBad(Exception): pass
217
218 -class Auth(policies.AuthAllowCall):
219 - def __init__(self, environ = None):
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 = { } # 'methodname' : ([list-of-valid-users],[list-of-valid-groups],[list-of-non-valid-users]) 242 self.permsobject = {} 243 if environ is None: environ = DefaultAuthEnviron 244 self.environ = environ
245
246 - def public_authenticator(self):
247 """ Creates a public authentity manager """ 248 return PublicAuthenticator(self.environ)
249
250 - def _auth(self, req):
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 # warn.debug(locals()) 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: # It is not a protected method 285 perm = None 286 if perm == True: 287 raise AuthGood, "Method is public" # always can call 288 if perm == False: 289 raise AuthBad, "Method is private" # never can call 290 if not self.environ.authenticate(authuser, authstring): 291 raise AuthBad, "Authentication is not valid to this user" 292 # 0: Allowed users 293 # 1: Allowed groups 294 # 2: Not allowed users 295 if permobj and (authuser in permobj[2]): 296 raise AuthBad, "User denied at object level" 297 # Method level checking 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 # Check if is allowed by the object 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