-- This script may be used with the auth-filter. Be sure to configure it as you wish.---- Requirements:-- luaossl-- <http://25thandclement.com/~william/projects/luaossl.html>-- lualdap >= 1.2-- <https://git.zx2c4.com/lualdap/about/>-- luaposix-- <https://github.com/luaposix/luaposix>--localsysstat=require("posix.sys.stat")localunistd=require("posix.unistd")locallualdap=require("lualdap")localrand=require("openssl.rand")localhmac=require("openssl.hmac")------ Configure these variables for your settings.------ A list of password protected repositories, with which gentooAccess-- group is allowed to access each one.localprotected_repos={glouglou="infra",portage="dev"}-- Set this to a path this script can write to for storing a persistent-- cookie secret, which should be guarded.localsecret_filename="/var/cache/cgit/auth-secret"------ Authentication functions follow below. Swap these out if you want different authentication semantics.------ Sets HTTP cookie headers based on post and sets up redirection.functionauthenticate_post()localredirect=validate_value("redirect",post["redirect"])ifredirect==nilthennot_found()return0endredirect_to(redirect)localgroups=gentoo_ldap_user_groups(post["username"],post["password"])ifgroups==nilthenset_cookie("cgitauth","")else-- One week expiration timeset_cookie("cgitauth",secure_value("gentoogroups",table.concat(groups,","),os.time()+604800))endhtml("\n")return0end-- Returns 1 if the cookie is valid and 0 if it is not.functionauthenticate_cookie()localrequired_group=protected_repos[cgit["repo"]]ifrequired_group==nilthen-- We return as valid if the repo is not protected.return1endlocaluser_groups=validate_value("gentoogroups",get_cookie(http["cookie"],"cgitauth"))ifuser_groups==niloruser_groups==""thenreturn0endforgroupinstring.gmatch(user_groups,"[^,]+")doifgroup==required_groupthenreturn1endendreturn0end-- Prints the html for the login form.functionbody()html("<h2>Gentoo LDAP Authentication Required</h2>")html("<form method='post' action='")html_attr(cgit["login"])html("'>")html("<input type='hidden' name='redirect' value='")html_attr(secure_value("redirect",cgit["url"],0))html("' />")html("<table>")html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")html("</table></form>")return0end------ Gentoo LDAP support.----functiongentoo_ldap_user_groups(username,password)-- Ensure the user is alphanumericifusername==nilorusername:match("%W")thenreturnnilendlocalwho="uid="..username..",ou=devs,dc=gentoo,dc=org"localldap,err=lualdap.open_simple{uri="ldap://ldap1.gentoo.org",who=who,password=password,starttls=true,certfile="/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.crt",keyfile="/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.key",cacertfile="/var/www/uwsgi/cgit/gentoo-ldap/ca.pem"}ifldap==nilthenreturnnilendlocalgroup_suffix=".group"localgroup_suffix_len=group_suffix:len()localgroups={}fordn,attribsinldap:search{base=who,scope="subtree"}dolocalaccess=attribs["gentooAccess"]ifdn==whoandaccess~=nilthenfori,vinipairs(access)dolocalvlen=v:len()ifvlen>group_suffix_lenandv:sub(-group_suffix_len)==group_suffixthentable.insert(groups,v:sub(1,vlen-group_suffix_len))endendendendldap:close()returngroupsend------ Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.----localactions={}actions["authenticate-post"]=authenticate_postactions["authenticate-cookie"]=authenticate_cookieactions["body"]=bodyfunctionfilter_open(...)action=actions[select(1,...)]http={}http["cookie"]=select(2,...)http["method"]=select(3,...)http["query"]=select(4,...)http["referer"]=select(5,...)http["path"]=select(6,...)http["host"]=select(7,...)http["https"]=select(8,...)cgit={}cgit["repo"]=select(9,...)cgit["page"]=select(10,...)cgit["url"]=select(11,...)cgit["login"]=select(12,...)endfunctionfilter_close()returnaction()endfunctionfilter_write(str)post=parse_qs(str)end------ Utility functions based on keplerproject/wsapi.----functionurl_decode(str)ifnotstrthenreturn""endstr=string.gsub(str,"+"," ")str=string.gsub(str,"%%(%x%x)",function(h)returnstring.char(tonumber(h,16))end)str=string.gsub(str,"\r\n","\n")returnstrendfunctionurl_encode(str)ifnotstrthenreturn""endstr=string.gsub(str,"\n","\r\n")str=string.gsub(str,"([^%w ])",function(c)returnstring.format("%%%02X",string.byte(c))end)str=string.gsub(str," ","+")returnstrendfunctionparse_qs(qs)localtab={}forkey,valinstring.gmatch(qs,"([^&=]+)=([^&=]*)&?")dotab[url_decode(key)]=url_decode(val)endreturntabendfunctionget_cookie(cookies,name)cookies=string.gsub(";"..cookies..";","%s*;%s*",";")returnstring.match(cookies,";"..name.."=(.-);")endfunctiontohex(b)localx=""fori=1,#bdox=x..string.format("%.2x",string.byte(b,i))endreturnxend------ Cookie construction and validation helpers.----localsecret=nil-- Loads a secret from a file, creates a secret, or returns one from memory.functionget_secret()ifsecret~=nilthenreturnsecretendlocalsecret_file=io.open(secret_filename,"r")ifsecret_file==nilthenlocalold_umask=sysstat.umask(63)localtemporary_filename=secret_filename..".tmp."..tohex(rand.bytes(16))localtemporary_file=io.open(temporary_filename,"w")iftemporary_file==nilthenos.exit(177)endtemporary_file:write(tohex(rand.bytes(32)))temporary_file:close()unistd.link(temporary_filename,secret_filename)-- Intentionally fails in the case that another process is doing the same.unistd.unlink(temporary_filename)sysstat.umask(old_umask)secret_file=io.open(secret_filename,"r")endifsecret_file==nilthenos.exit(177)endsecret=secret_file:read()secret_file:close()ifsecret:len()~=64thenos.exit(177)endreturnsecretend-- Returns value of cookie if cookie is valid. Otherwise returns nil.functionvalidate_value(expected_field,cookie)locali=0localvalue=""localfield=""localexpiration=0localsalt=""localchmac=""ifcookie==nilorcookie:len()<3orcookie:sub(1,1)=="|"thenreturnnilendforcomponentinstring.gmatch(cookie,"[^|]+")doifi==0thenfield=componentelseifi==1thenvalue=componentelseifi==2thenexpiration=tonumber(component)ifexpiration==nilthenexpiration=-1endelseifi==3thensalt=componentelseifi==4thenchmac=componentelsebreakendi=i+1endifchmac==nilorchmac:len()==0thenreturnnilend-- Lua hashes strings, so these comparisons are time invariant.ifchmac~=tohex(hmac.new(get_secret(),"sha256"):final(field.."|"..value.."|"..tostring(expiration).."|"..salt))thenreturnnilendifexpiration==-1or(expiration~=0andexpiration<=os.time())thenreturnnilendifurl_decode(field)~=expected_fieldthenreturnnilendreturnurl_decode(value)endfunctionsecure_value(field,value,expiration)ifvalue==nilorvalue:len()<=0thenreturn""endlocalauthstr=""localsalt=tohex(rand.bytes(16))value=url_encode(value)field=url_encode(field)authstr=field.."|"..value.."|"..tostring(expiration).."|"..saltauthstr=authstr.."|"..tohex(hmac.new(get_secret(),"sha256"):final(authstr))returnauthstrendfunctionset_cookie(cookie,value)html("Set-Cookie: "..cookie.."="..value.."; HttpOnly")ifhttp["https"]=="yes"orhttp["https"]=="on"orhttp["https"]=="1"thenhtml("; secure")endhtml("\n")endfunctionredirect_to(url)html("Status: 302 Redirect\n")html("Cache-Control: no-cache, no-store\n")html("Location: "..url.."\n")endfunctionnot_found()html("Status: 404 Not Found\n")html("Cache-Control: no-cache, no-store\n\n")end