site/api/lib/aaa.lua (76 lines of code) (raw):
--[[
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]
--[[
This is aaa.lua - generic AAA module.
It includes a basic version of the API.
However the default does not grant any rights.
Each site must provide their own customised AAA module.
The site-specific module must be called 'aaa_site.lua'
and be located in the lib/ directory.
]]
local config = require 'lib/config'
local aaa_site = nil
pcall(function() aaa_site = require 'lib/aaa_site' end)
--[[
The module is expected to return the following:
{
rights = function(r, account) to get the rights (required)
validateParams = true/false (optional)
canAccessList = function override (optional)
canAccessDoc = function override (optional)
}
]]
--Basic parameter validation
local function validateParams(r, account)
if not account.credentials then
return false -- no credentials, cannot grant rights
end
-- Check that we used oauth, bail if not
local oauth_domain = account.internal and account.internal.oauth_used or nil
if not oauth_domain then
return false -- no valid auth
end
-- check if oauth was through an oauth portal that can give privacy rights
local authority = false
for k, v in pairs(config.admin_oauth or {}) do
if r.strcmp_match(oauth_domain, v) then
authority = true
break
end
end
-- if not a 'good' oauth, then let's forget all about it
if not authority then
return false
end
-- if the uid exists, then validate it
local uid = account.credentials.uid
if uid and (not uid:match("^[-a-zA-Z0-9._]+$") or uid:sub(1,1) == '-') then
return false
end
-- TODO is there any further common validation possible?
-- not sure it makes sense to validate an email address here;
-- if required it should be done by the site module
return true
end
--[[
Get the set of rights to be used for checking access to private documents.
The default implementation returns an empty set of rights.
]]
local function getRights(r, account)
if aaa_site then -- we have a site override module
-- should we pre-validate the params?
if aaa_site.validateParams then
if not validateParams(r, account) then
return {}
end
end
return aaa_site.rights(r, account)
else
return {}
end
end
--[[
parse a list-id of the form "<name.dom.ain>"
returns the full lid, listname and the domain
The listname is assumed to be the leading characters upto the first '.'
The domain is the rest. The full lid is the whole input, without < and >.
]]
local function parseLid(lid)
return lid:match("^<(([^.]+)%.(.+))>$")
end
--[[
Does the account have the rights to access the mailing list?
This implementation checks the full list name and domain for an
exact match with one of the rights entries.
A rights entry of '*' matches all lists.
There is no wild-card matching apart from the '*' special case.
The name and domain are as determined by the parseLid function.
N.B. will fail if account or lid are invalid
]]
local function canAccessList(r, lid, account)
if aaa_site and aaa_site.canAccessList then -- we have a site override method
return aaa_site.canAccessList(r, lid, account) -- delegate to it
end
if not account then return false end
-- check the rights cache
local rights = account._rights_ -- get cached version
if not rights then
rights = getRights(r, account)
account._rights_ = rights -- cache them
end
-- we don't need the name
local flid, _ , domain = parseLid(lid)
for _, v in pairs(rights) do
if v == "*" or v == flid or v == domain then
return true
end
end
return false
end
--[[
does the account have the rights to access the document?
This implementation assumes that access is based on the list-id only,
so delegates the check to canAccessList.
N.B. will fail if doc is invalid
]]
local function canAccessDoc(r, doc, account)
if aaa_site and aaa_site.canAccessDoc then -- we have a site override method
return aaa_site.canAccessDoc(r, doc, account) -- delegate to it
end
if doc.private then
-- if not account then return false end (done by canAccessList)
-- assume that rights are list-based
return canAccessList(r, doc.list_raw, account)
else
return true
end
end
--[[
Note that the functions do not check their parameters.
This is because they may be called frequently.
TODO consider whether to replace the relevant local functions entirely
rather than using delegation if the aaa_site module provides them.
This would need to be done here, at the end of this module.
]]
-- module defs
return {
rights = getRights,
parseLid = parseLid,
canAccessList = canAccessList,
canAccessDoc = canAccessDoc
}