| -- 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 | |
| -- | |
| -- https://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. | |
| local Config = require("src/config/Config") | |
| local Policy = require("src/model/Policy") | |
| local Assertion = require("src/model/Assertion") | |
| local Util = require("src/util/Util") | |
| local Model = Policy:new() | |
| function Model:new() | |
| local o = {} | |
| setmetatable(o, self) | |
| self.__index = self | |
| o.model = {} | |
| o.sectionNameMap = { | |
| ["r"] = "request_definition", | |
| ["p"] = "policy_definition", | |
| ["g"] = "role_definition", | |
| ["e"] = "policy_effect", | |
| ["m"] = "matchers" | |
| } | |
| o.requiredSections = {"r", "p", "e", "m"} -- Minimal required sections for a model to be valid | |
| o.modCount = 0 -- used by CoreEnforcer to detect changes to Model | |
| -- PolicyOperations: [key] = POLICY_ADD/POLICY_REMOVE and value = string(key) | |
| o.PolicyOperations = { | |
| POLICY_ADD = "POLICY_ADD", | |
| POLICY_REMOVE = "POLICY_REMOVE" | |
| } | |
| return o | |
| end | |
| function Model:getModCount() | |
| return self.modCount | |
| end | |
| function Model:loadAssertion(model, cfg, sec, key) | |
| local value = cfg:getString(self.sectionNameMap[sec].."::"..key) | |
| return model:addDef(sec, key, value) | |
| end | |
| --[[ | |
| * addDef adds an assertion to the model. | |
| * | |
| * @param sec the section, "p" or "g". | |
| * @param key the policy type, "p", "p2", .. or "g", "g2", .. | |
| * @param value the policy rule, separated by ", ". | |
| * @return succeeds or not. | |
| ]] | |
| function Model:addDef(sec, key, value) | |
| if value == "" then return false end | |
| if self.model[sec] == nil then | |
| self.model[sec] = {} | |
| end | |
| if self.model[sec][key] == nil then | |
| self.model[sec][key] = {} | |
| end | |
| self.model[sec][key] = Assertion:new() | |
| self.model[sec][key].key = key | |
| self.model[sec][key].value = value | |
| self.model[sec][key].policyMap={} | |
| self.model[sec][key]:initPriorityIndex() | |
| if sec == "r" or sec == "p" then | |
| self.model[sec][key].tokens = Util.splitCommaDelimited(self.model[sec][key].value) | |
| for k, v in pairs(self.model[sec][key].tokens) do | |
| self.model[sec][key].tokens[k] = key .. "_" .. self.model[sec][key].tokens[k] | |
| end | |
| else | |
| self.model[sec][key].value = Util.removeComments(Util.escapeAssertion(self.model[sec][key].value)) | |
| end | |
| if sec == "m" and string.find(self.model[sec][key].value,"in")~=nil then | |
| self.model[sec][key].value = string.gsub(string.gsub(self.model[sec][key].value,"%[","("),"%]",")") | |
| end | |
| self.modCount = self.modCount + 1 | |
| return true | |
| end | |
| function Model:getKeySuffix(i) | |
| if i == 1 then | |
| return "" | |
| end | |
| return ""..i | |
| end | |
| function Model:loadSection(model, cfg, sec) | |
| local i = 1 | |
| while true do | |
| if not self:loadAssertion(model, cfg, sec, sec..self:getKeySuffix(i)) then | |
| break; | |
| else | |
| i = i + 1 | |
| end | |
| end | |
| end | |
| --[[ | |
| * loadModel loads the model from model CONF file. | |
| * | |
| * @param path the path of the model file. | |
| ]] | |
| function Model:loadModel(path) | |
| local cfg = Config:newConfig(path) | |
| self:loadSection(self, cfg, "r") | |
| self:loadSection(self, cfg, "p") | |
| self:loadSection(self, cfg, "e") | |
| self:loadSection(self, cfg, "m") | |
| self:loadSection(self, cfg, "g") | |
| end | |
| --[[ | |
| * loadModelFromText loads the model from the text. | |
| * | |
| * @param text the model text. | |
| ]] | |
| function Model:loadModelFromText(text) | |
| local cfg = Config:newConfigFromText(text) | |
| self:loadSection(self, cfg, "r") | |
| self:loadSection(self, cfg, "p") | |
| self:loadSection(self, cfg, "e") | |
| self:loadSection(self, cfg, "m") | |
| self:loadSection(self, cfg, "g") | |
| end | |
| --[[ | |
| * saveSectionToText saves the section to the text. | |
| * | |
| * @return the section text. | |
| ]] | |
| function Model:saveSectionToText(sec) | |
| local res = "[" .. self.sectionNameMap[sec] .. "]\n" | |
| if not self.model[sec] then | |
| return "" | |
| end | |
| for key, ast in pairs(self.model[sec]) do | |
| local val = ast.value:gsub("%_", ".") | |
| local x = string.format("%s = %s\n", key, val) | |
| res = res .. x | |
| end | |
| return res | |
| end | |
| --[[ | |
| * toText saves the model to the text. | |
| * | |
| * @return the model text. | |
| ]] | |
| function Model:toText() | |
| local tokenPatterns={} | |
| for _,ptype in pairs({"r","p"}) do | |
| for _,token in pairs(self.model[ptype][ptype].tokens) do | |
| tokenPatterns[token]=string.gsub (string.gsub (token,"^p_","p."),"^r_","r.") | |
| end | |
| end | |
| tokenPatterns["p_eft"] = "p.eft" | |
| local s="" | |
| local writeString=function(sec) | |
| local result="" | |
| for ptype,_ in pairs(self.model[sec]) do | |
| local value=self.model[sec][ptype].value | |
| for tokenPattern,newToken in pairs(tokenPatterns) do | |
| value=string.gsub(value,tokenPattern,newToken) | |
| end | |
| result=result..sec.." = "..value.."\n" | |
| end | |
| return result | |
| end | |
| s=s.."[request_definition]\n"..writeString("r").."[policy_definition]\n"..writeString("p") | |
| if self.model["g"] then | |
| s=s.."[role_definition]\n" | |
| for ptype,_ in pairs(self.model["g"]) do | |
| s=s..ptype.." = "..self.model["g"][ptype].value.."\n" | |
| end | |
| end | |
| s=s.."[policy_effect]\n"..writeString("e").."[matchers]\n"..writeString("m") | |
| return s | |
| end | |
| -- * printModel prints the model to the log. | |
| function Model:printModel() | |
| self.logger:info("Model: \n") | |
| for k,v in pairs(self.model) do | |
| for k2, v2 in pairs(v) do | |
| self.logger:info("[%s.%s]:", k, k2) | |
| self.logger:info(v2) | |
| end | |
| end | |
| end | |
| local function getSubjectHierarchyMap(policies) | |
| local subjectHierarchyMap = { } | |
| --Tree structure of role | |
| local policyMap = { } | |
| for _, policy in pairs(policies) do | |
| if #policy < 2 then | |
| return nil, error("policy g expect 2 more params") | |
| end | |
| local domain="" | |
| if #policy~=2 then | |
| domain = policy[3] | |
| end | |
| local child = domain.."::"..policy[1] | |
| local parent = domain.."::"..policy[2] | |
| if policyMap[parent]==nil then | |
| policyMap[parent]={} | |
| end | |
| table.insert(policyMap[parent], child) | |
| if subjectHierarchyMap[child]==nil then | |
| subjectHierarchyMap[child] = 0 | |
| end | |
| if subjectHierarchyMap[parent]==nil then | |
| subjectHierarchyMap[parent] = 0 | |
| end | |
| subjectHierarchyMap[child] = 1 | |
| end | |
| local queue = { } | |
| for k, v in pairs(subjectHierarchyMap) do | |
| if v == 0 then | |
| local root = k | |
| local lv = 0 | |
| table.insert(queue,root) | |
| while #queue~=0 do | |
| local sz=#queue | |
| for i=1,sz do | |
| local node=queue[1] | |
| table.remove(queue,1) | |
| subjectHierarchyMap[node] = lv | |
| if policyMap[node]~=nil then | |
| for _,child in pairs(policyMap[node]) do | |
| table.insert(queue,child) | |
| end | |
| end | |
| end | |
| lv=lv+1 | |
| end | |
| end | |
| end | |
| return subjectHierarchyMap, nil | |
| end | |
| function Model:sortPoliciesBySubjectHierarchy() | |
| if self.model["e"]["e"].value ~= "subjectPriority(p_eft) || deny" then | |
| return nil | |
| end | |
| local subIndex = 1 | |
| local domainIndex = -1 | |
| for ptype, assertion in pairs(self.model["p"]) do | |
| for index, token in pairs(assertion.tokens) do | |
| if token == ptype.."_dom" then | |
| domainIndex = index | |
| break | |
| end | |
| end | |
| local subjectHierarchyMap, err = getSubjectHierarchyMap(self.model["g"]["g"].policy) | |
| if err ~= nil then | |
| return err | |
| end | |
| table.sort(assertion.policy, function(i, j) | |
| local domain1, domain2 = "", "" | |
| if domainIndex ~= -1 then | |
| domain1 = i[domainIndex] | |
| domain2 = j[domainIndex] | |
| end | |
| local name1, name2 =domain1.."::"..i[subIndex], domain2.."::".. j[subIndex] | |
| local p1 = subjectHierarchyMap[name1] | |
| local p2 = subjectHierarchyMap[name2] | |
| return p1 > p2 | |
| end) | |
| for i, policy in pairs(assertion.policy) do | |
| assertion.policyMap[table.concat(policy, ",")] = i | |
| end | |
| end | |
| return nil | |
| end | |
| -- sortPoliciesByPriority sorts policies by their priorities if 'priority' token exists | |
| function Model:sortPoliciesByPriority() | |
| if not self.model["p"] then return end | |
| for ptype, ast in pairs(self.model["p"]) do | |
| for inx, token in pairs(ast.tokens) do | |
| if token == ptype .. "_priority" then | |
| ast.priorityIndex = inx | |
| break | |
| end | |
| end | |
| if ast.priorityIndex == -1 then | |
| return | |
| end | |
| table.sort(ast.policy, function (a, b) | |
| return a[ast.priorityIndex] < b[ast.priorityIndex] | |
| end) | |
| for i,policy in pairs(ast.policy) do | |
| ast.policyMap[table.concat(policy,",")]=i | |
| end | |
| end | |
| end | |
| function Model:copy() | |
| return Util.deepCopy(self.model) | |
| end | |
| return Model |