Auth.pm 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. package Trog::Auth;
  2. use strict;
  3. use warnings;
  4. no warnings 'experimental';
  5. use feature qw{signatures};
  6. use DBI;
  7. use DBD::SQLite;
  8. use File::Slurper qw{read_text};
  9. use UUID::Tiny ':std';
  10. use Digest::SHA 'sha256';
  11. =head1 Trog::Auth
  12. An SQLite3 authdb.
  13. =head1 Termination Conditions
  14. Throws exceptions in the event the session database cannot be accessed.
  15. =head1 FUNCTIONS
  16. =head2 session2user(sessid) = (STRING, INT)
  17. Translate a session UUID into a username and id.
  18. Returns empty strings on no active session.
  19. =cut
  20. sub session2user ($sessid) {
  21. my $dbh = _dbh();
  22. my $rows = $dbh->selectall_arrayref("SELECT name,id FROM sess_user WHERE session=?",{ Slice => {} }, $sessid);
  23. return ('','') unless ref $rows eq 'ARRAY' && @$rows;
  24. return ($rows->[0]->{name},$rows->[0]->{id});
  25. }
  26. =head2 acls4user(user_id) = ARRAYREF
  27. Return the list of ACLs belonging to the user.
  28. The function of ACLs are to allow you to access content tagged 'private' which are also tagged with the ACL name.
  29. The 'admin' ACL is the only special one, as it allows for authoring posts, configuring tCMS, adding series (ACLs) and more.
  30. =cut
  31. sub acls4user($user_id) {
  32. my $dbh = _dbh();
  33. my $records = $dbh->selectall_arrayref("SELECT acl FROM user_acl WHERE user_id = ?", { Slice => {} }, $user_id);
  34. return () unless ref $records eq 'ARRAY' && @$records;
  35. my @acls = map { $_->{acl} } @$records;
  36. return \@acls;
  37. }
  38. =head2 mksession(user, pass) = STRING
  39. Create a session for the user and waste all other sessions.
  40. Returns a session ID, or blank string in the event the user does not exist or incorrect auth was passed.
  41. =cut
  42. sub mksession ($user,$pass) {
  43. my $dbh = _dbh();
  44. my $records = $dbh->selectall_arrayref("SELECT salt FROM user WHERE name = ?", { Slice => {} }, $user);
  45. return '' unless ref $records eq 'ARRAY' && @$records;
  46. my $salt = $records->[0]->{salt};
  47. my $hash = sha256($pass.$salt);
  48. my $worked = $dbh->selectall_arrayref("SELECT id FROM user WHERE hash=? AND name = ?", { Slice => {} }, $hash, $user);
  49. return '' unless ref $worked eq 'ARRAY' && @$worked;
  50. my $uid = $worked->[0]->{id};
  51. my $uuid = create_uuid_as_string(UUID_V1, UUID_NS_DNS);
  52. $dbh->do("INSERT OR REPLACE INTO session (id,user_id) VALUES (?,?)", undef, $uuid, $uid) or return '';
  53. return $uuid;
  54. }
  55. =head2 useradd(user, pass) = BOOL
  56. Adds a user identified by the provided password into the auth DB.
  57. Returns True or False (likely false when user already exists).
  58. =cut
  59. sub useradd ($user, $pass, $acls) {
  60. my $dbh = _dbh();
  61. my $salt = create_uuid();
  62. my $hash = sha256($pass.$salt);
  63. my $res = $dbh->do("INSERT INTO user (name,salt,hash) VALUES (?,?,?)", undef, $user, $salt, $hash);
  64. return unless $res && ref $acls eq 'ARRAY';
  65. #XXX this is clearly not normalized with an ACL mapping table, will be an issue with large number of users
  66. foreach my $acl (@$acls) {
  67. return unless $dbh->do("INSERT INTO user_acl (user_id,acl) VALUES ((SELECT id FROM user WHERE name=?),?)", undef, $user, $acl);
  68. }
  69. return 1;
  70. }
  71. my $dbh;
  72. # Ensure the db schema is OK, and give us a handle
  73. sub _dbh {
  74. return $dbh if $dbh;
  75. my $qq = read_text('schema/auth.schema');
  76. my $dbname = "$ENV{HOME}/.tcms/auth.db";
  77. $dbh = DBI->connect("dbi:SQLite:dbname=$dbname","","");
  78. $dbh->{sqlite_allow_multiple_statements} = 1;
  79. $dbh->do($qq) or die "Could not ensure auth database consistency";
  80. $dbh->{sqlite_allow_multiple_statements} = 0;
  81. return $dbh;
  82. }
  83. 1;