schema.html

  1<!DOCTYPE html>
  2<html>
  3<head>
  4<meta charset="utf-8">
  5<title>Attribute-based Access Control</title>
  6<script>
  7!function(){var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
  8(function(){function R(a){function d(e){var b=e.charCodeAt(0);if(b!==92)return b;var a=e.charAt(1);return(b=r[a])?b:"0"<=a&&a<="7"?parseInt(e.substring(1),8):a==="u"||a==="x"?parseInt(e.substring(2),16):e.charCodeAt(1)}function g(e){if(e<32)return(e<16?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return e==="\\"||e==="-"||e==="]"||e==="^"?"\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),e=[],a=
  9b[0]==="^",c=["["];a&&c.push("^");for(var a=a?1:0,f=b.length;a<f;++a){var h=b[a];if(/\\[bdsw]/i.test(h))c.push(h);else{var h=d(h),l;a+2<f&&"-"===b[a+1]?(l=d(b[a+2]),a+=2):l=h;e.push([h,l]);l<65||h>122||(l<65||h>90||e.push([Math.max(65,h)|32,Math.min(l,90)|32]),l<97||h>122||e.push([Math.max(97,h)&-33,Math.min(l,122)&-33]))}}e.sort(function(e,a){return e[0]-a[0]||a[1]-e[1]});b=[];f=[];for(a=0;a<e.length;++a)h=e[a],h[0]<=f[1]+1?f[1]=Math.max(f[1],h[1]):b.push(f=h);for(a=0;a<b.length;++a)h=b[a],c.push(g(h[0])),
 10h[1]>h[0]&&(h[1]+1>h[0]&&c.push("-"),c.push(g(h[1])));c.push("]");return c.join("")}function s(e){for(var a=e.source.match(/\[(?:[^\\\] ]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),c=a.length,d=[],f=0,h=0;f<c;++f){var l=a[f];l==="("?++h:"\\"===l.charAt(0)&&(l=+l.substring(1))&&(l<=h?d[l]=-1:a[f]=g(l))}for(f=1;f<d.length;++f)-1===d[f]&&(d[f]=++x);for(h=f=0;f<c;++f)l=a[f],l==="("?(++h,d[h]||(a[f]="(?:")):"\\"===l.charAt(0)&&(l=+l.substring(1))&&l<=h&&
 11(a[f]="\\"+d[l]);for(f=0;f<c;++f)"^"===a[f]&&"^"!==a[f+1]&&(a[f]="");if(e.ignoreCase&&m)for(f=0;f<c;++f)l=a[f],e=l.charAt(0),l.length>=2&&e==="["?a[f]=b(l):e!=="\\"&&(a[f]=l.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return a.join("")}for(var x=0,m=!1,j=!1,k=0,c=a.length;k<c;++k){var i=a[k];if(i.ignoreCase)j=!0;else if(/[a-z]/i.test(i.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){m=!0;j=!1;break}}for(var r={b:8,t:9,n:10,v:11,
 12f:12,r:13},n=[],k=0,c=a.length;k<c;++k){i=a[k];if(i.global||i.multiline)throw Error(""+i);n.push("(?:"+s(i)+")")}return RegExp(n.join("|"),j?"gi":"g")}function S(a,d){function g(a){var c=a.nodeType;if(c==1){if(!b.test(a.className)){for(c=a.firstChild;c;c=c.nextSibling)g(c);c=a.nodeName.toLowerCase();if("br"===c||"li"===c)s[j]="\n",m[j<<1]=x++,m[j++<<1|1]=a}}else if(c==3||c==4)c=a.nodeValue,c.length&&(c=d?c.replace(/\r\n?/g,"\n"):c.replace(/[\t\n\r ]+/g," "),s[j]=c,m[j<<1]=x,x+=c.length,m[j++<<1|1]=
 13a)}var b=/(?:^|\s)nocode(?:\s|$)/,s=[],x=0,m=[],j=0;g(a);return{a:s.join("").replace(/\n$/,""),d:m}}function H(a,d,g,b){d&&(a={a:d,e:a},g(a),b.push.apply(b,a.g))}function T(a){for(var d=void 0,g=a.firstChild;g;g=g.nextSibling)var b=g.nodeType,d=b===1?d?a:g:b===3?U.test(g.nodeValue)?a:d:d;return d===a?void 0:d}function D(a,d){function g(a){for(var j=a.e,k=[j,"pln"],c=0,i=a.a.match(s)||[],r={},n=0,e=i.length;n<e;++n){var z=i[n],w=r[z],t=void 0,f;if(typeof w==="string")f=!1;else{var h=b[z.charAt(0)];
 14if(h)t=z.match(h[1]),w=h[0];else{for(f=0;f<x;++f)if(h=d[f],t=z.match(h[1])){w=h[0];break}t||(w="pln")}if((f=w.length>=5&&"lang-"===w.substring(0,5))&&!(t&&typeof t[1]==="string"))f=!1,w="src";f||(r[z]=w)}h=c;c+=z.length;if(f){f=t[1];var l=z.indexOf(f),B=l+f.length;t[2]&&(B=z.length-t[2].length,l=B-f.length);w=w.substring(5);H(j+h,z.substring(0,l),g,k);H(j+h+l,f,I(w,f),k);H(j+h+B,z.substring(B),g,k)}else k.push(j+h,w)}a.g=k}var b={},s;(function(){for(var g=a.concat(d),j=[],k={},c=0,i=g.length;c<i;++c){var r=
 15g[c],n=r[3];if(n)for(var e=n.length;--e>=0;)b[n.charAt(e)]=r;r=r[1];n=""+r;k.hasOwnProperty(n)||(j.push(r),k[n]=q)}j.push(/[\S\s]/);s=R(j)})();var x=d.length;return g}function v(a){var d=[],g=[];a.tripleQuotedStrings?d.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?d.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
 16q,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&g.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var b=a.hashComments;b&&(a.cStyleComments?(b>1?d.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):d.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),g.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,q])):d.push(["com",
 17/^#[^\n\r]*/,q,"#"]));a.cStyleComments&&(g.push(["com",/^\/\/[^\n\r]*/,q]),g.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));if(b=a.regexLiterals){var s=(b=b>1?"":"\n\r")?".":"[\\S\\s]";g.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+s+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+
 18s+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&g.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&g.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),q]);d.push(["pln",/^\s+/,q," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");g.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,
 19q],["pun",RegExp(b),q]);return D(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if("br"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d=
 20c?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,j=a.ownerDocument,k=j.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i<c.length;++i)b(c[i]);d===(d|0)&&c[0].setAttribute("value",d);var r=j.createElement("ol");
 21r.className="linenums";for(var d=Math.max(0,d-1|0)||0,i=0,n=c.length;i<n;++i)k=c[i],k.className="L"+(i+d)%10,k.firstChild||k.appendChild(j.createTextNode("\u00a0")),r.appendChild(k);a.appendChild(r)}function p(a,d){for(var g=d.length;--g>=0;){var b=d[g];F.hasOwnProperty(b)?E.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*</.test(d)?"default-markup":"default-code";return F[a]}function K(a){var d=a.h;try{var g=S(a.c,a.i),b=g.a;
 22a.a=b;a.d=g.d;a.e=0;I(d,b)(a);var s=/\bMSIE\s(\d+)/.exec(navigator.userAgent),s=s&&+s[1]<=8,d=/\n/g,x=a.a,m=x.length,g=0,j=a.d,k=j.length,b=0,c=a.g,i=c.length,r=0;c[i]=m;var n,e;for(e=n=0;e<i;)c[e]!==c[e+2]?(c[n++]=c[e++],c[n++]=c[e++]):e+=2;i=n;for(e=n=0;e<i;){for(var p=c[e],w=c[e+1],t=e+2;t+2<=i&&c[t+1]===w;)t+=2;c[n++]=p;c[n++]=w;e=t}c.length=n;var f=a.c,h;if(f)h=f.style.display,f.style.display="none";try{for(;b<k;){var l=j[b+2]||m,B=c[r+2]||m,t=Math.min(l,B),A=j[b+1],G;if(A.nodeType!==1&&(G=x.substring(g,
 23t))){s&&(G=G.replace(d,"\r"));A.nodeValue=G;var L=A.ownerDocument,o=L.createElement("span");o.className=c[r+1];var v=A.parentNode;v.replaceChild(o,A);o.appendChild(A);g<l&&(j[b+1]=A=L.createTextNode(x.substring(t,l)),v.insertBefore(A,o.nextSibling))}g=t;g>=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){E.console&&console.log(u&&u.stack||u)}}var E=window,y=["break,continue,do,else,for,if,return,while"],C=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
 24"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],M=[C,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],V=[C,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],
 25N=[C,"abstract,as,base,bool,by,byte,checked,decimal,delegate,descending,dynamic,event,finally,fixed,foreach,from,group,implicit,in,interface,internal,into,is,let,lock,null,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],C=[C,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],O=[y,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
 26P=[y,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],W=[y,"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"],y=[y,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],Q=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,
 27U=/\S/,X=v({keywords:[M,N,C,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",O,P,y],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),F={};p(X,["default-code"]);p(D([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",
 28/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i] ]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);p(D([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"] ],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],
 29["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i] ]),["in.tag"]);p(D([],[["atv",/^[\S\s]+/] ]),["uq.val"]);p(v({keywords:M,hashComments:!0,cStyleComments:!0,types:Q}),["c","cc","cpp","cxx","cyc","m"]);p(v({keywords:"null,true,false"}),["json"]);p(v({keywords:N,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:Q}),
 30["cs"]);p(v({keywords:V,cStyleComments:!0}),["java"]);p(v({keywords:y,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);p(v({keywords:O,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);p(v({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);p(v({keywords:P,
 31hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);p(v({keywords:C,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);p(v({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);p(v({keywords:W,cStyleComments:!0,multilineStrings:!0}),["rc","rs","rust"]);
 32p(D([],[["str",/^[\S\s]+/] ]),["regex"]);var Y=E.PR={createSimpleLexer:D,registerLangHandler:p,sourceDecorator:v,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:E.prettyPrintOne=function(a,d,g){var b=document.createElement("div");b.innerHTML="<pre>"+a+"</pre>";b=b.firstChild;g&&J(b,g,!0);K({h:d,j:g,c:b,i:1});
 33return b.innerHTML},prettyPrint:E.prettyPrint=function(a,d){function g(){for(var b=E.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;i<p.length&&c.now()<b;i++){for(var d=p[i],j=h,k=d;k=k.previousSibling;){var m=k.nodeType,o=(m===7||m===8)&&k.nodeValue;if(o?!/^\??prettify\b/.test(o):m!==3||/\S/.test(k.nodeValue))break;if(o){j={};o.replace(/\b(\w+)=([\w%+\-.:]+)/g,function(a,b,c){j[b]=c});break}}k=d.className;if((j!==h||e.test(k))&&!v.test(k)){m=!1;for(o=d.parentNode;o;o=o.parentNode)if(f.test(o.tagName)&&
 34o.className&&e.test(o.className)){m=!0;break}if(!m){d.className+=" prettyprinted";m=j.lang;if(!m){var m=k.match(n),y;if(!m&&(y=T(d))&&t.test(y.tagName))m=y.className.match(n);m&&(m=m[1])}if(w.test(d.tagName))o=1;else var o=d.currentStyle,u=s.defaultView,o=(o=o?o.whiteSpace:u&&u.getComputedStyle?u.getComputedStyle(d,q).getPropertyValue("white-space"):0)&&"pre"===o.substring(0,3);u=j.linenums;if(!(u=u==="true"||+u))u=(u=k.match(/\blinenums\b(?::(\d+))?/))?u[1]&&u[1].length?+u[1]:!0:!1;u&&J(d,u,o);r=
 35{h:m,c:d,j:u,i:o};K(r)}}}i<p.length?setTimeout(g,250):"function"===typeof a&&a()}for(var b=d||document.body,s=b.ownerDocument||document,b=[b.getElementsByTagName("pre"),b.getElementsByTagName("code"),b.getElementsByTagName("xmp")],p=[],m=0;m<b.length;++m)for(var j=0,k=b[m].length;j<k;++j)p.push(b[m][j]);var b=q,c=Date;c.now||(c={now:function(){return+new Date}});var i=0,r,n=/\blang(?:uage)?-([\w.]+)(?!\S)/,e=/\bprettyprint\b/,v=/\bprettyprinted\b/,w=/pre|xmp/i,t=/^code$/i,f=/^(?:pre|code|xmp)$/i,
 36h={};g()}};typeof define==="function"&&define.amd&&define("google-code-prettify",[],function(){return Y})})();}()
 37</script>
 38<style>
 39.pln{color:#1b181b}.str{color:#918b3b}.kwd{color:#7b59c0}.com{color:#9e8f9e}.typ{color:#516aec}.lit{color:#a65926}.clo,.opn,.pun{color:#1b181b}.tag{color:#ca402b}.atn{color:#a65926}.atv{color:#159393}.dec{color:#a65926}.var{color:#ca402b}.fun{color:#516aec}pre.prettyprint{background:#f7f3f7;color:#ab9bab;font-family:Menlo,Consolas,"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace;font-size:12px;line-height:1.5;border:1px solid #d8cad8;padding:10px}ol.linenums{margin-top:0;margin-bottom:0}
 40body{min-width:200px;max-width:850px;margin:0 auto;padding:30px;}.chapter-nav{font-size: 10pt;}a:link,a:visited{color:#00f}.codeblock_name,code,pre.prettyprint{font-family:Monaco,"Lucida Console",monospace}body{font-size:14pt}.codeblock_name,.math,.seealso,code{font-size:10pt}.codeblock{page-break-inside:avoid;padding-bottom:15px}.math{text-indent:0}pre.prettyprint{font-size:10pt;padding:10px;border-radius:10px;border:none;white-space:pre-wrap}.codeblock_name{margin-top:1.25em;display:block}a:link{text-decoration:none}a:link:not(.lit):hover{color:#00f;text-decoration:underline}a:link:active{color:red}h4{padding-right:1.25em}h4.noheading{margin-bottom:0}h1{text-align:center}code{padding:2px}pre{-moz-tab-size:4;-o-tab-size:4;tab-size:4}p:not(.notp){margin:0;text-indent:2em}.two-col{list-style-type:none}.two-col li:before{content:'-';padding:5px;margin-right:5px;color:orange;background-color:#fff;display:inline-block}@media print{body{font-size:10pt}pre.prettyprint{font-size:8pt}.seealso{font-size:9pt}.codeblock_name,.math,code{font-size:8pt}.math{text-indent:0}}
 41</style>
 42</head>
 43<body onload="prettyPrint()">
 44<section>
 45<h1>Attribute-based Access Control</h1>
 46<a name="1:1"><div class="section"><h4>1. Schema</h4></a>
 47<p>The basic schema consists of three tables and a materialized view of the permissions set.  Goose is used to handle
 48migrations.
 49</p>
 50<p>The <code>permissions</code> table uses a zanzibar-style system.  There should be one answer to the question: Can <code>{actor}</code> do <code>{action}</code>
 51to <code>{resource}</code>.  Permissions are simplified to a bit-field integer (see permissions.go).  Resources have a <code>type</code> and
 52an <code>id</code> (a int64 snowflake primary key).  For example, in a blog system the type might be <code>blog</code> corresponding to a
 53path of <code>blog/{article_id}</code>.  Resource type must always be provided, but the id may be NULL.  Nulls are interpreted as
 54matching any id.  So, a row that contains a permission for <code>type=blog id=NULL</code> is the default permission for that actor unless
 55for any blog resource type unless a more specific permission is given to a resource by id.
 56</p>
 57<p>The actor can be one of two types: either an id for a specific actor (user id) or a reference to an attribute that defines
 58a group of one or more users.  In this way, permissions can be granted based on the specifics of the user or a persona
 59created by an attribute.
 60</p>
 61
 62<div class="codeblock">
 63<span class="codeblock_name">{<strong>schema/001_base.sql</strong> <a href="schema.html#1:1">1</a>}</span>
 64<pre class="prettyprint lang-sql">
 65-- +goose Up
 66-- +goose StatementBegin
 67CREATE TABLE IF NOT EXISTS permissions (
 68  resource_type TEXT NOT NULL,
 69  resource_id INTEGER,
 70  user_id INTEGER,
 71  attribute_id INTEGER,
 72  permissions INTEGER NOT NULL
 73);
 74
 75</pre>
 76
 77
 78
 79</div>
 80<p>Attributes are unique key, value pairs of any type.
 81</p>
 82
 83<div class="codeblock">
 84<span class="codeblock_name">{<strong>schema/001_base.sql</strong> <a href="schema.html#1:1">1</a>} +=</span>
 85<pre class="prettyprint lang-sql">
 86CREATE TABLE IF NOT EXISTS attributes (key TEXT NOT NULL, value BLOB NOT NULL);
 87
 88</pre>
 89
 90
 91
 92</div>
 93<p>To assign one or more users to an attribute, a join table connects the unique key,value attribute with a user ID.
 94</p>
 95
 96<div class="codeblock">
 97<span class="codeblock_name">{<strong>schema/001_base.sql</strong> <a href="schema.html#1:1">1</a>} +=</span>
 98<pre class="prettyprint lang-sql">
 99CREATE TABLE IF NOT EXISTS attribute_user (
100  attribute_id INTEGER NOT NULL,
101  user_id INTEGER NOT NULL
102);
103
104</pre>
105
106
107
108</div>
109<p>A materialized view creates a table <code>abac</code> that consists of permissions rules associated either with a specific
110<code>user_id</code> or with NULL fields that represent default permissions for that <code>resource_type</code> or <code>resource_id</code>.  The table
111is joined with the attributes table to create a permissions row for each user and resource.  For example, if permissions
112are given to the attribute 'role: users' and you add give that attribute to three users, the materialized view creates
113three rows in the table, one for each <code>resource-&gt;user-&gt;permission</code> that is derived from the attribute permission.
114</p>
115
116<div class="codeblock">
117<span class="codeblock_name">{<strong>schema/001_base.sql</strong> <a href="schema.html#1:1">1</a>} +=</span>
118<pre class="prettyprint lang-sql">
119CREATE VIEW IF NOT EXISTS abac (resource_type, resource_id, user_id, permissions) AS
120SELECT
121  permissions.resource_type,
122  permissions.resource_id,
123  COALESCE(permissions.user_id, attribute_user.user_id) AS user_id,
124  permissions.permissions
125FROM
126  permissions
127  LEFT JOIN attribute_user USING (attribute_id);
128
129</pre>
130
131
132
133</div>
134<p>Unique indexes are used to ensure that there is a single unambigious row for each resource and user combination.
135</p>
136
137<div class="codeblock">
138<span class="codeblock_name">{<strong>schema/001_base.sql</strong> <a href="schema.html#1:1">1</a>} +=</span>
139<pre class="prettyprint lang-sql">
140CREATE UNIQUE INDEX IF NOT EXISTS permission_idx_user ON permissions (resource_type, resource_id, user_id)
141WHERE
142  attribute_id IS NULL;
143
144CREATE UNIQUE INDEX IF NOT EXISTS permission_idx_att ON permissions (resource_type, resource_id, attribute_id)
145WHERE
146  user_id IS NULL;
147
148CREATE UNIQUE INDEX IF NOT EXISTS permission_idx_def_res ON permissions (resource_type, resource_id)
149WHERE
150  user_id IS NULL
151  AND attribute_id IS NULL;
152
153CREATE UNIQUE INDEX IF NOT EXISTS permission_idx_def_type ON permissions (resource_type)
154WHERE
155  resource_id IS NULL
156  AND user_id IS NULL
157  AND attribute_id IS NULL;
158
159CREATE UNIQUE INDEX IF NOT EXISTS attribute_user_idx ON attribute_user (attribute_id, user_id);
160
161CREATE UNIQUE INDEX IF NOT EXISTS attribute_idx ON attributes (key, value);
162-- +goose StatementEnd
163
164</pre>
165
166
167
168</div>
169<p>And a corresponding rollback is provided for migrations.
170</p>
171
172<div class="codeblock">
173<span class="codeblock_name">{<strong>schema/001_base.sql</strong> <a href="schema.html#1:1">1</a>} +=</span>
174<pre class="prettyprint lang-sql">
175-- +goose Down
176-- +goose StatementBegin
177DROP TABLE permissions;
178DROP TABLE attributes;
179DROP TABLE attribute_user;
180DROP INDEX permission_idx_user;
181DROP INDEX permission_idx_att;
182DROP INDEX permission_idx_def_res;
183DROP INDEX permission_idx_def_type;
184DROP INDEX attribute_user_idx;
185DROP INDEX attribute_idx;
186-- +goose StatementEnd
187</pre>
188
189
190
191</div>
192<p><h1>Adding and querying permissions</h1>
193</p>
194
195</div>
196<a name="1:2"><div class="section"><h4>2. Setting the default for a type</h4></a>
197<p>ABAC provides a system for a fallback "default" permission to be set for a particular resource type or a specific
198resource identified by ID.  This is a namespaced system where every resource is defined as <code>type:id</code> or can be mapped to
199URLs as <code>type/id</code>.  For example, a blog may be identified by <code>blog/{id}</code>.  To define default permissions for a type,
200fields <code>resource_id</code>, <code>user_id</code>, and <code>attribute_id</code> are <code>NULL</code>.
201</p>
202
203<div class="codeblock">
204<span class="codeblock_name">{<strong>set_default_for_type.sql</strong> <a href="schema.html#1:2">2</a>}</span>
205<pre class="prettyprint lang-sql">
206INSERT INTO permissions (resource_type, permissions, resource_id, user_id)
207	VALUES (?, ?, NULL, NULL)
208ON CONFLICT
209	DO UPDATE SET permissions=excluded.permissions;
210</pre>
211
212
213
214</div>
215</div>
216<a name="1:3"><div class="section"><h4>3. Setting the default for a resource</h4></a>
217<p>Similarly, a particular resource identified by <code>type/id</code> can have a default permission set that applies to all users
218unless a more specific rule is associated with an actor.
219</p>
220
221<div class="codeblock">
222<span class="codeblock_name">{<strong>set_default_for_resource.sql</strong> <a href="schema.html#1:3">3</a>}</span>
223<pre class="prettyprint lang-sql">
224INSERT INTO permissions (resource_type, resource_id, permissions, user_id)
225	VALUES (?, ?, ?, NULL)
226ON CONFLICT
227	DO UPDATE SET permissions=excluded.permissions;
228</pre>
229
230
231
232</div>
233</div>
234<a name="1:4"><div class="section"><h4>4. Setting a permission for a user</h4></a>
235<p>For a specific actor, permissions can apply to a particular resource identified by <code>resource_id</code> or a class of resources
236identified by <code>resource_type</code>.
237</p>
238
239<div class="codeblock">
240<span class="codeblock_name">{<strong>set_permission_for_user.sql</strong> <a href="schema.html#1:4">4</a>}</span>
241<pre class="prettyprint lang-sql">
242INSERT INTO permissions (resource_type, resource_id, user_id, permissions)
243	VALUES (?, ?, ?, ?)
244ON CONFLICT
245	DO UPDATE SET permissions=excluded.permissions;
246</pre>
247
248
249
250</div>
251<p>A user can also have a default permission for a type.  This is often be used for blanket rules (e.g., banning an actor from accessing a class of resources or controlling administrative routes.)
252</p>
253
254<div class="codeblock">
255<span class="codeblock_name">{<strong>set_default_for_user.sql</strong> <a href="schema.html#1:4">4</a>}</span>
256<pre class="prettyprint lang-sql">
257INSERT INTO permissions (resource_type, resource_id, user_id, permissions)
258	VALUES (?, NULL, ?, ?)
259ON CONFLICT
260	DO UPDATE SET permissions=excluded.permissions;
261</pre>
262
263
264
265</div>
266</div>
267<a name="1:5"><div class="section"><h4>5. Setting a permission for an attribute</h4></a>
268<p>Similarly, permissions can be set for an attribute.  In the database, creation the attribute and assigning the permission
269happens in a transaction and uses conflict rules to ensure uniqueness.  This provides a simplified interface for consumers
270who don't need to care about the database schema and can think instead of key/value attributes and permissions.
271</p>
272
273<div class="codeblock">
274<span class="codeblock_name">{<strong>set_permission_for_attribute.sql</strong> <a href="schema.html#1:5">5</a>}</span>
275<pre class="prettyprint lang-sql">
276BEGIN TRANSACTION;
277INSERT INTO attributes (key, value) VALUES (?, ?) ON CONFLICT DO NOTHING;
278INSERT INTO permissions (resource_type, resource_id, attribute_id, permissions)
279	VALUES (
280			?,
281			?,
282			(SELECT rowid FROM attributes WHERE key=? AND value=?),
283			?
284			)
285ON CONFLICT
286	DO UPDATE SET permissions=excluded.permissions;
287COMMIT;
288</pre>
289
290
291
292</div>
293<p>An attribute can also be used to set the default permissions for a type.
294</p>
295
296<div class="codeblock">
297<span class="codeblock_name">{<strong>set_default_for_attribute.sql</strong> <a href="schema.html#1:5">5</a>}</span>
298<pre class="prettyprint lang-sql">
299BEGIN TRANSACTION;
300INSERT INTO attributes (key, value) VALUES (?, ?) ON CONFLICT DO NOTHING;
301INSERT INTO permissions (resource_type, resource_id, attribute_id, permissions)
302	VALUES (
303			?,
304			NULL,
305			(SELECT rowid FROM attributes WHERE key=? AND value=?),
306			?
307			)
308ON CONFLICT
309	DO UPDATE SET permissions=excluded.permissions;
310COMMIT;
311</pre>
312
313
314
315</div>
316<p>Attributes are set on a user and created in the database if needed.
317</p>
318
319<div class="codeblock">
320<span class="codeblock_name">{<strong>set_attribute_for_user.sql</strong> <a href="schema.html#1:5">5</a>}</span>
321<pre class="prettyprint lang-sql">
322BEGIN TRANSACTION;
323INSERT INTO attributes (key, value) VALUES (?, ?) ON CONFLICT DO NOTHING;
324INSERT INTO attribute_user (attribute_id, user_id)
325	VALUES (
326			(SELECT rowid FROM attributes WHERE key=? AND value=?)
327			, ?)
328ON CONFLICT
329	DO NOTHING;
330COMMIT;
331</pre>
332
333
334
335</div>
336</div>
337<a name="1:6"><div class="section"><h4>6. Querying permissions for a user</h4></a>
338<p>Querying makes use of the database schema and ordering behavior to return a single permission that reflects
339this precedence:
340</p>
341<ul>
342<li>(1) exact match of <code>type/id</code> with a rule that resolves to the <code>user_id</code> directly or from an attribute (highest permission wins)</li>
343</li>
344<li>(2) default for the <code>resource_id</code></li>
345</li>
346<li>(3) default for the <code>resource_type</code></li>
347</li>
348</ul>
349
350<div class="codeblock">
351<span class="codeblock_name">{<strong>get_permission.sql</strong> <a href="schema.html#1:6">6</a>}</span>
352<pre class="prettyprint lang-sql">
353SELECT permissions FROM abac
354WHERE
355	resource_type=?
356AND
357	(resource_id=? OR resource_id IS NULL)
358AND
359	(user_id=? OR user_id IS NULL)
360ORDER BY
361	user_id NULLS LAST,
362	resource_id NULLS LAST
363LIMIT 1;
364</pre>
365
366
367
368</div>
369
370</div>
371</body>