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 }