def main(argv=sys.argv): if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: pass
def requested(cls, action): if isinstance(action, Action): return action in cls.requested(action.__class__) return (DBSession.query(action) .join(AccessRecord) .filter(AccessRecord.capability == None) .distinct())
def __check(action_class, serial): req = DBSession.query(action_class).filter(Action.serial == serial).first() if req is None: return HTTPNotFound("Invalid serial number") if Access.processed(req, True): return req.render("approved", True) if Access.processed(req, False): return req.render("denied", True) if Access.filtered(req): return req.render("pending", True) return req.render("rejected", True)
def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ # Parse the CA settings (must occur before creating the Configurator) Secrets.parse_config(settings) RevokeDB.parse_config(settings) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) config = Configurator(settings=settings) session_factory = session_factory_from_settings(settings) config.set_session_factory(session_factory) authn_policy = AuthTktAuthenticationPolicy(AUTH_SECRET, secure=AUTH_SECURE, http_only=True, include_ip=True, cookie_name=AUTH_COOKIE, wild_domain=False, timeout=AUTH_TIMEOUT, reissue_time=AUTH_REISSUE, callback=capability_finder) authz_policy = CapabilityAuthorizationPolicy() config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.add_route('test', '/test') config.add_route('login', '/login') config.add_route('logout', '/logout') config.add_route('crl', '/crl') config.add_route('request', '/{type}/request') config.add_route('check', '/{type}/check') config.add_route('review', '/{type}/review') config.add_route('revoke', '/{type}/revoke') config.scan() return config.make_wsgi_app()
def usable(cls, t=None, user=None, action_class=None, access_types=None): t = time() // 1 if t is None else t query = DBSession.query(cls) if user is not None: query = query.filter(Capability.user == user) if action_class is not None: query = query.filter(Capability.action_class == action_class) if access_types is not None: query = query.filter(Capability.access_type.in_(access_types)) query = ( query.filter(Capability.revoked == None) .filter(or_(Capability.start_time == None, Capability.start_time <= t)) .filter(or_(Capability.end_time == None, Capability.end_time >= t)) ) return [cap for cap in query if cap.valid()]
def processed(cls, action, success=True): if isinstance(action, Action): if action not in cls.processed(action.__class__, success=None): return False elif success is None: return True else: return success != (action in cls.processed(action.__class__, success=False)) query = (DBSession.query(action) .join(AccessRecord).join(Capability) .filter(AccessRecord.allowed == True)) if success is None: query = query.filter(Capability.access_type.in_(EXIT)) else: fail_query = query.filter(Capability.access_type == EXIT[1]) if success: query = query.filter(Capability.access_type == EXIT[0]).except_(fail_query) else: query = fail_query return query.distinct()
def review_page(request, action_class, **kwargs): access = Access(request) allowable = access.allowable(action_class) if allowable is False: simple = action_class.readable() raise HTTPForbidden("You don't have sufficient permissions to review %s requests" % simple) serial_field = 'SERIAL' answer = '' POST = request.POST if serial_field in POST and (EXIT[0] in POST or EXIT[1] in POST): serial = POST[serial_field] action = DBSession.query(action_class).filter(Action.serial == serial).first() if action is None: raise HTTPNotFound('Invalid serial number') if action not in allowable: raise HTTPForbidden('Action not available for processing') if EXIT[0] in POST and EXIT[1] in POST: raise ValueError('Both "%s" and "%s" specified in form' % EXIT) choice = EXIT[1] if EXIT[1] in POST else EXIT[0] caps = [c for c in allowable[action] if c.access_type == choice] try: answer = access.perform_with_one(action, caps) except HTTPException as e: answer = e.detail else: del allowable[action] forms = [] form_params = dict(serial_field=serial_field) button_options = {EXIT[0]:'Allow', EXIT[1]:'Deny'} for action, caps in allowable.iteritems(): render_template, render_params = action.render('pending') form_params['info'] = HTML(render(render_template, render_params, request)) form_params['serial'] = action.serial form_params['credentials'] = offer_creds(request, caps) choices = set((c.access_type for c in caps)) form_params['buttons'] = ((c, button_options[c]) for c in choices) forms.append(HTML(render(FORM_TEMPLATE, form_params, request))) if not forms: forms.append('No requests are available for processing') return dict(forms=forms, answer=HTML(answer), **kwargs)
def revoke_page(request, action_class, **kwargs): access = Access(request) revocable = access.revocable(action_class) if revocable is False: simple = action_class.readable() raise HTTPForbidden("You don't have sufficient permissions to revoke %s requests" % simple) serial_field = 'SERIAL' answer = '' POST = request.POST if serial_field in POST: serial = POST[serial_field] action = DBSession.query(action_class).filter(Action.serial == serial).first() if action is None: raise HTTPNotFound('Invalid serial number') if action not in revocable: raise HTTPForbidden('Action not available for revocation') try: answer = access.perform_with_one(action, revocable[action]) except HTTPException as e: answer = e.detail else: del revocable[action] forms = [] form_params = dict(serial_field=serial_field, button='Revoke') button_options = {EXIT[0]:'Allow', EXIT[1]:'Deny'} for action, caps in revocable.iteritems(): render_template, render_params = action.render('approved') form_params['info'] = HTML(render(render_template, render_params, request)) form_params['serial'] = action.serial form_params['credentials'] = offer_creds(request, caps) forms.append(HTML(render(FORM_TEMPLATE, form_params, request))) if not forms: forms.append('No requests are available for revocation') return dict(forms=forms, answer=HTML(answer), **kwargs)
def __perform(self, action, capability, vetted=False, value=None): # Don't perform the access again if self.__performed: raise HTTPForbidden('Cannot perform access again') allowed = True try: if capability is None: at = ENTER else: at = capability.access_type # Verify that the action is allowed by the capability if not vetted: cons = capability.constraint if cons is not None and not cons.allows(action, self): raise HTTPForbidden('Invoked capability does not allow this access') if at in EXIT: # Make sure that the action has been filtered before making any attempt to # process its execution if not vetted and not self.filtered(action): raise HTTPForbidden('Action not available for processing') # If the access is an approval for processing, check that the action has not # been processed before, then perform it if at == EXIT[0]: if self.processed(action, None): raise HTTPForbidden('Action not available for processing') return action.perform(self.request) # Otherwise, if this action has already been approved, this is a revocation if self.processed(action): return action.revoke(self.request) # Otherwise, logging this access is enough to mark the action as denied return HTTPForbidden(FILTER_DENY) if vetted else 'Request marked as denied' elif at in FILTER: # Filtering is only done automatically if not vetted: raise RuntimeError('Request filtering must be automatic') if at == FILTER[0]: # If this access is letting the action through the filter, failure to # take further automatic action should simply stop execution. Possible # automatic accesses are drawn from the EXIT list types = EXIT filters = [] # If this is an authenticated request, try proceeding with the requestor's # positive access capabilities. If none exist, then move on to auto-filters if self.user is not None: filters = self.own_processes(action) if not filters: filters = self.processes(action, True) def fail(msg): return value else: # Otherwise, logging this access is enough to mark the action as rejected return HTTPForbidden(FILTER_REJECT) elif at == ENTER: # If this access is a new request, failure should raise an unauthorized # exception and store the failed access. Possible automatic accesses are # drawn from the FILTER list types = FILTER filters = [] # If this is an authenticated request, try proceeding with the requestor's # positive access capabilities. If none exist, then move on to auto-filters if self.user is not None: filters = self.own_filters(action) if not filters: filters = self.filters(action, True) value = HTTPAccepted(action.serial) def fail(msg): raise HTTPForbidden(msg) else: raise RuntimeError('%s is not a valid access type' % str(at)) # If no filters actually match the action, then the attempt at automatic # access should fail closed immediately (lack of a no does not mean yes) if not filters: return fail(NO_FILTERS) pos = [cap for cap in filters if cap.access_type == types[0]] neg = [cap for cap in filters if cap.access_type == types[1]] if neg: # If any negative filters were triggered, perform a negative access value = self.__perform(action, neg[0], True, value) DBSession.add_all((self.__record(action, cap, True) for cap in neg[1:])) DBSession.add_all((self.__record(action, cap, False) for cap in pos)) else: # Otherwise, perform a positive access value = self.__perform(action, pos[0], True, value) DBSession.add_all((self.__record(action, cap, True) for cap in pos[1:])) except: allowed = False raise finally: self.__save(action, capability, allowed) if value is None: raise RuntimeException('Illegal return state') return value
def _perform(self, action, capability): DBSession.add(action) ret = self.__perform(action, capability) self.__performed = True return ret
def own_processes(self, action): q1 = ((lambda(a): True) if isinstance(action, Action) else (lambda(a): DBSession.query(a))) filtered = (q1, lambda(a): self.processed(a, None)) return self.__acceptable(action, (EXIT[0],), filtered)
def setup_admin(request): """Provide a form view for configuring initial admin account settings""" # Name and maxLength of the email field mail_field = "email", User.email.property.columns[0].type.length # Names of the password/confirmation fields pass_fields = "pass1", "pass2", "pass3", "pass4" # Name of the submitted field submitted = "newuser.submitted" # Set the inputs and error message to empty strings email, passwords, message = "", ("", "", "", ""), "" # FIXME: Set defaults for easy testing email = "*****@*****.**" passwords = ("password", "password", "password1", "password1") # If the form was submitted, process the input if submitted in request.params: # Retrieve the input values email = request.POST[mail_field[0]] passwords = ( request.POST[pass_fields[0]].encode("utf-8"), request.POST[pass_fields[1]].encode("utf-8"), request.POST[pass_fields[2]].encode("utf-8"), request.POST[pass_fields[3]].encode("utf-8"), ) # Validate the email, and passwords message = validate_email(email) if not message: message = validate_passwords((passwords[0], passwords[1])) if not message: message = validate_passwords((passwords[2], passwords[3])) if not message and passwords[0] == passwords[2]: message = "ROOT and USERS passwords must be different" # If no error occurred, create the configured accounts if not message: # Create the ROOT user priv_root = User("ROOT", email, passwords[0]) DBSession.add(priv_root) # Give it an admin capability (can grant any capability) DBSession.add(AdminCapability(priv_root)) # Create the USERS user user_root = User("USERS", email, passwords[2]) DBSession.add(user_root) # Give it every capability related to NewUser requests for access_type in FILTER_ACCESS + PROCESS_ACCESS: grant = GrantCapability(user_root, NewUser, access_type) DBSession.add(grant) access = grant.grant(user_root) DBSession.add(access) # Redirect to the home page, logged in as USERS return HTTPFound(location=request.route_url("home"), headers=remember(request, "USERS")) # Return the render dictionary return dict( mail_field=mail_field, pass_fields=pass_fields, message=message, email=email, passwords=passwords, submitted=submitted, )
def _needs_admin(info, request): """Custom predicate which checks if an admin needs to be created""" return DBSession.query(User).count() == 0
def validate_username(username, allow_existing=False): if len(username) < 3: return 'Username must be at least 3 characters long' if not allow_existing and DBSession.query(User).filter(User.login == username).count() > 0: return 'Username already taken' return ''
def __save(self, *args, **kwargs): DBSession.add(self.__record(*args, **kwargs))
def own_filters(self, action): q1 = ((lambda(a): True) if isinstance(action, Action) else (lambda(a): DBSession.query(a))) requested = (q1, lambda(a): self.filtered(a, None)) return self.__acceptable(action, (FILTER[0],), requested)
def is_admin(self): """Check if this user is the ROOT admin account""" return DBSession.query(User).get(1) is self
def query(self, access_info, action_class=None): action_class = self._action_class(action_class) return DBSession.query(action_class).filter(self.condition(access_info, action_class))
def get(cls, userid): """Take a username and return the corresponding User, if it exists""" if userid: return DBSession.query(cls).filter(cls.login==userid).first() return None