<commit_before># -*- coding: utf-8 -*-

"""Provides SQLAlchemy based model classes."""

__all__ = [
    'Role',
    'User',
    'Email'
]

import os
from binascii import hexlify
from datetime import datetime

from passlib.apps import custom_app_context as pwd_context

from sqlalchemy import desc, event
from sqlalchemy import Column, ForeignKey, Table
from sqlalchemy import Boolean, DateTime, Integer, Unicode, UnicodeText
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import relationship

from zope.interface import implements

from pyramid.decorator import reify
from pyramid.security import ALL_PERMISSIONS
from pyramid.security import Allow, Deny
from pyramid.security import Authenticated, Everyone
from pyramid_basemodel import Base, BaseMixin, Session, save

from .interfaces import IEmail, IRole, IUser

def encrypt(raw_password):
    """Encrypt a raw password into a secure hash using passlib.
      
      Setup::
      
          >>> from mock import Mock
          >>> from pyramid_simpleauth import model
          >>> _pwd_context = model.pwd_context
          >>> model.pwd_context = Mock()
          >>> model.pwd_context.encrypt.return_value = 'digest'
      
      Returns a unicode version of the encrypted password::
      
          >>> encrypt('Foo ')
          u'digest'
      
      Using a lower case, stripped version of the raw password::
      
          >>> model.pwd_context.encrypt.call_args[0][0]
          'foo'
      
      Teardown::
      
          >>> model.pwd_context = _pwd_context
      
    """
    
    v = raw_password.strip().lower()
    s = pwd_context.encrypt(v, scheme="sha512_crypt", rounds=90000)
    return unicode(s)

def generate_random_digest(num_bytes=28):
    """Generates a random hash and returns the hex digest as a unicode string.
      
      Defaults to sha224::
      
          >>> import hashlib
          >>> h = hashlib.sha224()
          >>> digest = generate_random_digest()
          >>> len(h.hexdigest()) == len(digest)
          True
      
      Pass in ``num_bytes`` to specify a different length hash::
      
          >>> h = hashlib.sha512()
          >>> digest = generate_random_digest(num_bytes=64)
          >>> len(h.hexdigest()) == len(digest)
          True
      
      Returns unicode::
      
          >>> type(digest) == type(u'')
          True
      
    """
    
    r = os.urandom(num_bytes)
    return unicode(hexlify(r))


generate_canonical_id = lambda: generate_random_digest(num_bytes=64)
generate_confirmation_hash = lambda: generate_random_digest(num_bytes=14)

class Role(Base, BaseMixin):
    """Role a user may have (like admin or editor)."""
    
    implements(IRole)
    
    __tablename__ = 'auth_roles'
    
    name = Column(Unicode(33), unique=True)
    description = Column(Unicode(256))

class User(Base, BaseMixin):
    """Model class encapsulating a user."""
    
    implements(IUser)
    
    __tablename__ = 'auth_users'
    
    @property
    def __acl__(self):
        """Grants all permissions to ``self.username``::
          
              >>> user = User()
              >>> user.canonical_id = 'user_id'
              >>> user.__acl__[1] == (Allow, 'user_id', ALL_PERMISSIONS)
              True
        """
        
        return [
            (Allow, 'r:admin', ALL_PERMISSIONS),
            (Allow, self.canonical_id, ALL_PERMISSIONS),
            (Allow, Authenticated, 'view'),
            (Deny, Everyone, ALL_PERMISSIONS)
        ]
    
    canonical_id = Column(Unicode(128), default=generate_canonical_id, unique=True)
    username = Column(Unicode(32), unique=True)
    password = Column(Unicode(120))
    
    roles = relationship("Role", secondary="auth_users_to_roles", lazy="joined")
    emails = relationship("Email", lazy="joined", backref="user",
            cascade="all, delete-orphan", single_parent=True)
    
    @reify
    def is_admin(self):
        """Does the user have a role called 'r:admin'.
          
              >>> raise NotImplementedError
          
        """
        
        for item in self.roles:
            if item.name == 'admin':
                return True
        return False
    
    def __json__(self):
        """Return a dictionary representation of the ``User`` instance.
          
              >>> user = User(username='thruflo')
              >>> user.__json__()
              {'username': 'thruflo'}
          
        """
        
        return {'username': self.username}
    

class Email(Base, BaseMixin):
    """A user's email address with optional confirmation data."""
    
    implements(IEmail)
    
    __tablename__ = 'auth_emails'
    
    address = Column(Unicode(255), unique=True)<commit_msg>model: make `is_admin` a property and add doctest.<commit_after># -*- coding: utf-8 -*-

"""Provides SQLAlchemy based model classes."""

__all__ = [
    'Role',
    'User',
    'Email'
]

import os
from binascii import hexlify
from datetime import datetime

from passlib.apps import custom_app_context as pwd_context

from sqlalchemy import desc, event
from sqlalchemy import Column, ForeignKey, Table
from sqlalchemy import Boolean, DateTime, Integer, Unicode, UnicodeText
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import relationship

from zope.interface import implements

from pyramid.decorator import reify
from pyramid.security import ALL_PERMISSIONS
from pyramid.security import Allow, Deny
from pyramid.security import Authenticated, Everyone
from pyramid_basemodel import Base, BaseMixin, Session, save

from .interfaces import IEmail, IRole, IUser

def encrypt(raw_password):
    """Encrypt a raw password into a secure hash using passlib.
      
      Setup::
      
          >>> from mock import Mock
          >>> from pyramid_simpleauth import model
          >>> _pwd_context = model.pwd_context
          >>> model.pwd_context = Mock()
          >>> model.pwd_context.encrypt.return_value = 'digest'
      
      Returns a unicode version of the encrypted password::
      
          >>> encrypt('Foo ')
          u'digest'
      
      Using a lower case, stripped version of the raw password::
      
          >>> model.pwd_context.encrypt.call_args[0][0]
          'foo'
      
      Teardown::
      
          >>> model.pwd_context = _pwd_context
      
    """
    
    v = raw_password.strip().lower()
    s = pwd_context.encrypt(v, scheme="sha512_crypt", rounds=90000)
    return unicode(s)

def generate_random_digest(num_bytes=28):
    """Generates a random hash and returns the hex digest as a unicode string.
      
      Defaults to sha224::
      
          >>> import hashlib
          >>> h = hashlib.sha224()
          >>> digest = generate_random_digest()
          >>> len(h.hexdigest()) == len(digest)
          True
      
      Pass in ``num_bytes`` to specify a different length hash::
      
          >>> h = hashlib.sha512()
          >>> digest = generate_random_digest(num_bytes=64)
          >>> len(h.hexdigest()) == len(digest)
          True
      
      Returns unicode::
      
          >>> type(digest) == type(u'')
          True
      
    """
    
    r = os.urandom(num_bytes)
    return unicode(hexlify(r))


generate_canonical_id = lambda: generate_random_digest(num_bytes=64)
generate_confirmation_hash = lambda: generate_random_digest(num_bytes=14)

class Role(Base, BaseMixin):
    """Role a user may have (like admin or editor)."""
    
    implements(IRole)
    
    __tablename__ = 'auth_roles'
    
    name = Column(Unicode(33), unique=True)
    description = Column(Unicode(256))

class User(Base, BaseMixin):
    """Model class encapsulating a user."""
    
    implements(IUser)
    
    __tablename__ = 'auth_users'
    
    @property
    def __acl__(self):
        """Grants all permissions to ``self.username``::
          
              >>> user = User()
              >>> user.canonical_id = 'user_id'
              >>> user.__acl__[1] == (Allow, 'user_id', ALL_PERMISSIONS)
              True
        """
        
        return [
            (Allow, 'r:admin', ALL_PERMISSIONS),
            (Allow, self.canonical_id, ALL_PERMISSIONS),
            (Allow, Authenticated, 'view'),
            (Deny, Everyone, ALL_PERMISSIONS)
        ]
    
    canonical_id = Column(Unicode(128), default=generate_canonical_id, unique=True)
    username = Column(Unicode(32), unique=True)
    password = Column(Unicode(120))
    
    roles = relationship("Role", secondary="auth_users_to_roles", lazy="joined")
    emails = relationship("Email", lazy="joined", backref="user",
            cascade="all, delete-orphan", single_parent=True)
    
    @property
    def is_admin(self):
        """Does the user have a role called 'admin'?
          
          If the user doesn't have any roles, it's ``False``::
          
              >>> u = User()
              >>> u.is_admin
              False
          
          If they have roles but none named ``admin`` then returns ``False``::
          
              >>> r = Role(name='foo')
              >>> u.roles.append(r)
              >>> u.is_admin
              False
          
          If they do have a role named ``admin``, then returns ``True``::
          
              >>> r = Role(name='admin')
              >>> u.roles.append(r)
              >>> u.is_admin
              True
          
        """
        
        for item in self.roles:
            if item.name == 'admin':
                return True
        return False
    
    def __json__(self):
        """Return a dictionary representation of the ``User`` instance.
          
              >>> user = User(username='thruflo')
              >>> user.__json__()
              {'username': 'thruflo'}
          
        """
        
        return {'username': self.username}
    

class Email(Base, BaseMixin):
    """A user's email address with optional confirmation data."""
    
    implements(IEmail)
    
    __tablename__ = 'auth_emails'
    
    address = Column(Unicode(255), unique=True)
{'fraction_non_alphanumeric': 0.10993825931089424, 'fraction_numerical': 0.008962358095996813, 'mean_word_length': 2.189266433788504, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 156, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4646322', 'n_tokens_mistral': 2999, 'n_tokens_neox': 2845, 'n_words': 745}