--[[
Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]

local M = require('acf2.model')
local object = require('acf2.object')

local digest = require('crypto').digest


Role = M.new()
Role.permissions = M.Set{type=M.Reference{scope='../../../permissions'}}


local function hash_password(algorithm, salt, password)
   return algorithm..'$'..salt..'$'..digest(algorithm, salt..password)
end

local hash_pattern = '^(%w+)%$(%w+)%$%x+$'


local Password = object.class(M.String)

function Password:_validate(context, value)
   value = object.super(self, M.String):_validate(context, value)
   if not value or value:find(hash_pattern) then return value end

   local salt = ''
   for i = 1,12 do
      local c = math.random(48, 109)
      if c > 57 then c = c + 7 end
      if c > 90 then c = c + 6 end
      salt = salt..string.char(c)
   end
   return hash_password('sha256', salt, value)
end


User = M.new()
User.password = Password
User['real-name'] = M.String
User.superuser = M.Boolean{default=false}
User.roles = M.Set{type=M.Reference{scope='../../../roles'}}

function User:check_password(password)
   if not self.password then return false end
   local _, _, algorithm, salt = self.password:find(hash_pattern)
   if not salt then return false end
   return hash_password(algorithm, salt, password) == self.password
end

function User:check_permission(permission)
   -- TODO audit trail
   print('check permission', permission)

   if self.superuser then return true end

   assert(getmetatable(self).txn:fetch('/auth/permissions')[permission])

   for _, role in M.node.pairs(self.roles, true) do
      for _, p in M.node.pairs(role.permissions, true) do
	 if p == permission then return true end
      end
   end
   return false
end


Authentication = M.new()
Authentication.users = M.Collection{type=User}
Authentication.roles = M.Collection{type=Role}
Authentication.permissions = M.Set{
   type=M.String,
   addr='/volatile/aaa/permissions'
}

M.register(
   'auth',
   Authentication,
   {
      addr='/json'..require('posix').getcwd()..'/config/aaa.json',
      ui_name='Authentication'
   }
)

M.permission.defaults('/auth')
