auth.py 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. # -*- coding: utf-8 -*-
  2. """
  3. Auth* related model.
  4. This is where the models used by the authentication stack are defined.
  5. It's perfectly fine to re-use this definition in the pboard application,
  6. though.
  7. """
  8. import os
  9. from datetime import datetime
  10. from hashlib import sha256
  11. __all__ = ['User', 'Group', 'Permission']
  12. from sqlalchemy import Table, ForeignKey, Column
  13. from sqlalchemy.types import Unicode, Integer, DateTime
  14. from sqlalchemy.orm import relation, synonym
  15. from pboard.model import DeclarativeBase, metadata, DBSession
  16. # This is the association table for the many-to-many relationship between
  17. # groups and permissions.
  18. group_permission_table = Table('pod_group_permission', metadata,
  19. Column('group_id', Integer, ForeignKey('pod_group.group_id',
  20. onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
  21. Column('permission_id', Integer, ForeignKey('pod_permission.permission_id',
  22. onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
  23. )
  24. # This is the association table for the many-to-many relationship between
  25. # groups and members - this is, the memberships.
  26. user_group_table = Table('pod_user_group', metadata,
  27. Column('user_id', Integer, ForeignKey('pod_user.user_id',
  28. onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
  29. Column('group_id', Integer, ForeignKey('pod_group.group_id',
  30. onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
  31. )
  32. class Group(DeclarativeBase):
  33. """
  34. Group definition
  35. Only the ``group_name`` column is required.
  36. """
  37. __tablename__ = 'pod_group'
  38. group_id = Column(Integer, autoincrement=True, primary_key=True)
  39. group_name = Column(Unicode(16), unique=True, nullable=False)
  40. display_name = Column(Unicode(255))
  41. created = Column(DateTime, default=datetime.now)
  42. users = relation('User', secondary=user_group_table, backref='groups')
  43. def __repr__(self):
  44. return '<Group: name=%s>' % repr(self.group_name)
  45. def __unicode__(self):
  46. return self.group_name
  47. @classmethod
  48. def by_group_name(cls, group_name):
  49. """Return the user object whose email address is ``email``."""
  50. return DBSession.query(cls).filter_by(group_name=group_name).first()
  51. class User(DeclarativeBase):
  52. """
  53. User definition.
  54. This is the user definition used by :mod:`repoze.who`, which requires at
  55. least the ``email_address`` column.
  56. """
  57. __tablename__ = 'pod_user'
  58. user_id = Column(Integer, autoincrement=True, primary_key=True)
  59. email_address = Column(Unicode(255), unique=True, nullable=False)
  60. display_name = Column(Unicode(255))
  61. _password = Column('password', Unicode(128))
  62. created = Column(DateTime, default=datetime.now)
  63. def __repr__(self):
  64. return '<User: email=%s, display=%s>' % (
  65. repr(self.email_address), repr(self.display_name))
  66. def __unicode__(self):
  67. return self.display_name or self.email_address
  68. @property
  69. def permissions(self):
  70. """Return a set with all permissions granted to the user."""
  71. perms = set()
  72. for g in self.groups:
  73. perms = perms | set(g.permissions)
  74. return perms
  75. @classmethod
  76. def by_email_address(cls, email):
  77. """Return the user object whose email address is ``email``."""
  78. return DBSession.query(cls).filter_by(email_address=email).first()
  79. @classmethod
  80. def by_user_name(cls, username):
  81. """Return the user object whose user name is ``username``."""
  82. return DBSession.query(cls).filter_by(email_address=username).first()
  83. @classmethod
  84. def _hash_password(cls, password):
  85. salt = sha256()
  86. salt.update(os.urandom(60))
  87. salt = salt.hexdigest()
  88. hash = sha256()
  89. # Make sure password is a str because we cannot hash unicode objects
  90. hash.update((password + salt).encode('utf-8'))
  91. hash = hash.hexdigest()
  92. password = salt + hash
  93. # Make sure the hashed password is a unicode object at the end of the
  94. # process because SQLAlchemy _wants_ unicode objects for Unicode cols
  95. # FIXME - D.A. - 2013-11-20 - The following line has been removed since using python3. Is this normal ?!
  96. # password = password.decode('utf-8')
  97. return password
  98. def _set_password(self, password):
  99. """Hash ``password`` on the fly and store its hashed version."""
  100. self._password = self._hash_password(password)
  101. def _get_password(self):
  102. """Return the hashed version of the password."""
  103. return self._password
  104. password = synonym('_password', descriptor=property(_get_password,
  105. _set_password))
  106. def validate_password(self, password):
  107. """
  108. Check the password against existing credentials.
  109. :param password: the password that was provided by the user to
  110. try and authenticate. This is the clear text version that we will
  111. need to match against the hashed one in the database.
  112. :type password: unicode object.
  113. :return: Whether the password is valid.
  114. :rtype: bool
  115. """
  116. hash = sha256()
  117. hash.update((password + self.password[:64]).encode('utf-8'))
  118. return self.password[64:] == hash.hexdigest()
  119. class Permission(DeclarativeBase):
  120. """
  121. Permission definition.
  122. Only the ``permission_name`` column is required.
  123. """
  124. __tablename__ = 'pod_permission'
  125. permission_id = Column(Integer, autoincrement=True, primary_key=True)
  126. permission_name = Column(Unicode(63), unique=True, nullable=False)
  127. description = Column(Unicode(255))
  128. groups = relation(Group, secondary=group_permission_table,
  129. backref='permissions')
  130. def __repr__(self):
  131. return '<Permission: name=%s>' % repr(self.permission_name)
  132. def __unicode__(self):
  133. return self.permission_name