liquid_feedback_frontend
changeset 1071:58f48a8a202a
Imported and merged LDAP patch
line diff
1.1 --- a/app/main/_filter/21_auth.lua Thu Jul 17 23:38:35 2014 +0200 1.2 +++ b/app/main/_filter/21_auth.lua Fri Jul 18 21:42:59 2014 +0200 1.3 @@ -9,6 +9,7 @@ 1.4 or action == "login" 1.5 or view == "register" 1.6 or action == "register" 1.7 + or action == "cancel_register" 1.8 or view == "about" 1.9 or view == "reset_password" 1.10 or action == "reset_password"
2.1 --- a/app/main/_filter_view/30_navigation.lua Thu Jul 17 23:38:35 2014 +0200 2.2 +++ b/app/main/_filter_view/30_navigation.lua Fri Jul 18 21:42:59 2014 +0200 2.3 @@ -46,6 +46,9 @@ 2.4 } 2.5 2.6 slot.put ( " " ) 2.7 + end 2.8 + 2.9 + if app.session.member == nil and not config.registration_disabled then 2.10 2.11 ui.link { 2.12 text = _"Registration",
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/app/main/index/_action/cancel_register.lua Fri Jul 18 21:42:59 2014 +0200 3.3 @@ -0,0 +1,5 @@ 3.4 +app.session.authority = nil 3.5 +app.session.authority_data = nil 3.6 +app.session:save() 3.7 + 3.8 +return true 3.9 \ No newline at end of file
4.1 --- a/app/main/index/_action/login.lua Thu Jul 17 23:38:35 2014 +0200 4.2 +++ b/app/main/index/_action/login.lua Fri Jul 18 21:42:59 2014 +0200 4.3 @@ -1,7 +1,22 @@ 4.4 local login = param.get("login") 4.5 local password = param.get("password") 4.6 4.7 -local member = Member:by_login_and_password(login, password) 4.8 +local member, err, uid = Member:by_login_and_password(login, password) 4.9 + 4.10 +if err == "ldap_credentials_valid_but_no_member" then 4.11 + app.session.authority = "ldap" 4.12 + app.session.authority_data = encode.pg_hstore{ 4.13 + login = login, 4.14 + uid = uid 4.15 + } 4.16 + app.session:save() 4.17 + request.redirect{ 4.18 + module = "index", view = "register", params = { 4.19 + ldap_login = login 4.20 + } 4.21 + } 4.22 + return 4.23 +end 4.24 4.25 function do_etherpad_auth(member) 4.26 local result = net.curl(
5.1 --- a/app/main/index/_action/register.lua Thu Jul 17 23:38:35 2014 +0200 5.2 +++ b/app/main/index/_action/register.lua Fri Jul 18 21:42:59 2014 +0200 5.3 @@ -1,12 +1,26 @@ 5.4 local code = util.trim(param.get("code")) 5.5 5.6 -local member = Member:new_selector() 5.7 - :add_where{ "invite_code = ?", code } 5.8 - :add_where{ "activated ISNULL" } 5.9 - :add_where{ "NOT locked" } 5.10 - :optional_object_mode() 5.11 - :for_update() 5.12 - :exec() 5.13 +local member 5.14 + 5.15 +if app.session.authority == "ldap" then 5.16 + if not config.ldap.member or not config.ldap.member.registration == "manual" then 5.17 + error("access denied") 5.18 + end 5.19 + member = ldap.create_member(app.session.authority_data_uid, true) 5.20 + 5.21 +else 5.22 + if config.registration_disabled then 5.23 + error("registration disabled") 5.24 + end 5.25 + member = Member:new_selector() 5.26 + :add_where{ "invite_code = ?", code } 5.27 + :add_where{ "activated ISNULL" } 5.28 + :add_where{ "NOT locked" } 5.29 + :optional_object_mode() 5.30 + :for_update() 5.31 + :exec() 5.32 +end 5.33 + 5.34 5.35 if not member then 5.36 slot.put_into("error", _"The code you've entered is invalid") 5.37 @@ -20,7 +34,7 @@ 5.38 5.39 local notify_email = param.get("notify_email") 5.40 5.41 -if not config.locked_profile_fields.notify_email and notify_email then 5.42 +if not util.is_profile_field_locked(member, "notify_email") and notify_email then 5.43 if #notify_email < 5 then 5.44 slot.put_into("error", _"Email address too short!") 5.45 request.redirect{ 5.46 @@ -33,7 +47,7 @@ 5.47 end 5.48 end 5.49 5.50 -if member and not notify_email then 5.51 +if member and not util.is_profile_field_locked(member, "notify_email") and not notify_email then 5.52 request.redirect{ 5.53 mode = "redirect", 5.54 module = "index", 5.55 @@ -46,7 +60,7 @@ 5.56 5.57 local name = util.trim(param.get("name")) 5.58 5.59 -if not config.locked_profile_fields.name and name then 5.60 +if not util.is_profile_field_locked(member, "name") and name then 5.61 5.62 if #name < 3 then 5.63 slot.put_into("error", _"This screen name is too short!") 5.64 @@ -83,7 +97,7 @@ 5.65 5.66 end 5.67 5.68 -if notify_email and not member.name then 5.69 +if notify_email and not util.is_profile_field_locked(member, "name") and not member.name then 5.70 request.redirect{ 5.71 mode = "redirect", 5.72 module = "index", 5.73 @@ -97,10 +111,9 @@ 5.74 return false 5.75 end 5.76 5.77 - 5.78 local login = util.trim(param.get("login")) 5.79 5.80 -if not config.locked_profile_fields.login and login then 5.81 +if not util.is_profile_field_locked(member, "login") and login then 5.82 if #login < 3 then 5.83 slot.put_into("error", _"This login is too short!") 5.84 request.redirect{ 5.85 @@ -136,7 +149,7 @@ 5.86 member.login = login 5.87 end 5.88 5.89 -if member.name and not member.login then 5.90 +if member.name and not util.is_profile_field_locked(member, "login") and not member.login then 5.91 request.redirect{ 5.92 mode = "redirect", 5.93 module = "index", 5.94 @@ -163,40 +176,43 @@ 5.95 end 5.96 end 5.97 5.98 - local password1 = param.get("password1") 5.99 - local password2 = param.get("password2") 5.100 + if not member.authority == "ldap" then 5.101 + 5.102 + local password1 = param.get("password1") 5.103 + local password2 = param.get("password2") 5.104 5.105 - if login and not password1 then 5.106 - request.redirect{ 5.107 - mode = "redirect", 5.108 - module = "index", 5.109 - view = "register", 5.110 - params = { 5.111 - code = member.invite_code, 5.112 - notify_email = notify_email, 5.113 - name = member.name, 5.114 - login = member.login 5.115 + if login and not password1 then 5.116 + request.redirect{ 5.117 + mode = "redirect", 5.118 + module = "index", 5.119 + view = "register", 5.120 + params = { 5.121 + code = member.invite_code, 5.122 + notify_email = notify_email, 5.123 + name = member.name, 5.124 + login = member.login 5.125 + } 5.126 } 5.127 - } 5.128 - --]] 5.129 - return false 5.130 + --]] 5.131 + return false 5.132 + end 5.133 + 5.134 + if password1 ~= password2 then 5.135 + slot.put_into("error", _"Passwords don't match!") 5.136 + return false 5.137 + end 5.138 + 5.139 + if #password1 < 8 then 5.140 + slot.put_into("error", _"Passwords must consist of at least 8 characters!") 5.141 + return false 5.142 + end 5.143 end 5.144 5.145 - if password1 ~= password2 then 5.146 - slot.put_into("error", _"Passwords don't match!") 5.147 - return false 5.148 - end 5.149 - 5.150 - if #password1 < 8 then 5.151 - slot.put_into("error", _"Passwords must consist of at least 8 characters!") 5.152 - return false 5.153 - end 5.154 - 5.155 - if not config.locked_profile_fields.login then 5.156 + if not util.is_profile_field_locked(member, "login") then 5.157 member.login = login 5.158 end 5.159 5.160 - if not config.locked_profile_fields.name then 5.161 + if not util.is_profile_field_locked(member, "name") then 5.162 member.name = name 5.163 end 5.164 5.165 @@ -208,7 +224,9 @@ 5.166 end 5.167 end 5.168 5.169 - member:set_password(password1) 5.170 + if not member.authority == "ldap" then 5.171 + member:set_password(password1) 5.172 + end 5.173 5.174 local now = db:query("SELECT now() AS now", "object").now 5.175 5.176 @@ -221,7 +239,7 @@ 5.177 member.active = true 5.178 member.last_activity = 'now' 5.179 member:save() 5.180 - 5.181 + 5.182 slot.put_into("notice", _"You've successfully registered and you can login now with your login and password!") 5.183 5.184 request.redirect{
6.1 --- a/app/main/index/register.lua Thu Jul 17 23:38:35 2014 +0200 6.2 +++ b/app/main/index/register.lua Fri Jul 18 21:42:59 2014 +0200 6.3 @@ -1,3 +1,13 @@ 6.4 +local ldap_uid 6.5 + 6.6 +if config.ldap.member and app.session.authority == "ldap" then 6.7 + ldap_uid = app.session.authority_data_uid 6.8 +end 6.9 + 6.10 +if config.registration_disabled and not ldap_uid then 6.11 + error("registration disabled") 6.12 +end 6.13 + 6.14 execute.view{ module = "index", view = "_lang_chooser" } 6.15 6.16 local step = param.get("step", atom.integer) 6.17 @@ -6,6 +16,7 @@ 6.18 local name = param.get("name") 6.19 local login = param.get("login") 6.20 6.21 + 6.22 ui.form{ 6.23 attr = { class = "section vertical" }, 6.24 module = 'index', 6.25 @@ -18,7 +29,7 @@ 6.26 }, 6.27 content = function() 6.28 6.29 - if not code then 6.30 + if not code and not ldap_uid then 6.31 ui.field.hidden{ name = "step", value = 1 } 6.32 ui.title(_"Registration (step 1 of 3: Invite code)") 6.33 ui.sectionHead( function() 6.34 @@ -37,17 +48,32 @@ 6.35 ui.link{ 6.36 content = _"cancel registration", 6.37 module = "index", 6.38 - view = "index" 6.39 + action = "cancel_register", 6.40 + routing = { default = { 6.41 + mode = "redirect", module = "index", view = "index" 6.42 + } } 6.43 } 6.44 end ) 6.45 else 6.46 - local member = Member:new_selector() 6.47 - :add_where{ "invite_code = ?", code } 6.48 - :add_where{ "activated ISNULL" } 6.49 - :optional_object_mode() 6.50 - :exec() 6.51 + local member 6.52 + 6.53 + if ldap_uid then 6.54 + member, err = ldap.create_member(ldap_uid, true) 6.55 + if err then 6.56 + error(err) 6.57 + end 6.58 + else 6.59 + member = Member:new_selector() 6.60 + :add_where{ "invite_code = ?", code } 6.61 + :add_where{ "activated ISNULL" } 6.62 + :optional_object_mode() 6.63 + :exec() 6.64 + end 6.65 6.66 - if not member.notify_email and not notify_email or not member.name and not name or not member.login and not login or step == 1 then 6.67 + if (not member.notify_email and not notify_email) 6.68 + or (not member.name and not name) 6.69 + or (not member.login and not login and not member.authority) 6.70 + or step == 1 then 6.71 ui.title(_"Registration (step 2 of 3: Personal information)") 6.72 ui.field.hidden{ name = "step", value = 2 } 6.73 6.74 @@ -62,7 +88,7 @@ 6.75 6.76 execute.view{ module = "member", view = "_profile", params = { member = member, for_registration = true } } 6.77 6.78 - if not config.locked_profile_fields.notify_email then 6.79 + if not util.is_profile_field_locked(member, "notify_email") then 6.80 ui.tag{ 6.81 tag = "p", 6.82 content = _"Please enter your email address. This address will be used for automatic notifications (if you request them) and in case you've lost your password. This address will not be published. After registration you will receive an email with a confirmation link." 6.83 @@ -73,7 +99,7 @@ 6.84 value = param.get("notify_email") or member.notify_email 6.85 } 6.86 end 6.87 - if not config.locked_profile_fields.name then 6.88 + if not util.is_profile_field_locked(member, "name") then 6.89 ui.tag{ 6.90 tag = "p", 6.91 content = _"Please choose a name, i.e. your real name or your nick name. This name will be shown to others to identify you." 6.92 @@ -84,7 +110,7 @@ 6.93 value = param.get("name") or member.name 6.94 } 6.95 end 6.96 - if not config.locked_profile_fields.login then 6.97 + if not util.is_profile_field_locked(member, "login") then 6.98 ui.tag{ 6.99 tag = "p", 6.100 content = _"Please choose a login name. This name will not be shown to others and is used only by you to login into the system. The login name is case sensitive." 6.101 @@ -111,7 +137,10 @@ 6.102 ui.link{ 6.103 content = _"cancel registration", 6.104 module = "index", 6.105 - view = "index" 6.106 + action = "cancel_register", 6.107 + routing = { default = { 6.108 + mode = "redirect", module = "index", view = "index" 6.109 + } } 6.110 } 6.111 end ) 6.112 else 6.113 @@ -165,24 +194,27 @@ 6.114 6.115 slot.put("<br />") 6.116 6.117 - ui.tag{ 6.118 - tag = "p", 6.119 - content = _"Please choose a password and enter it twice. The password is case sensitive." 6.120 - } 6.121 - ui.field.text{ 6.122 - readonly = true, 6.123 - label = _'Login name', 6.124 - name = 'login', 6.125 - value = member.login 6.126 - } 6.127 - ui.field.password{ 6.128 - label = _'Password', 6.129 - name = 'password1', 6.130 - } 6.131 - ui.field.password{ 6.132 - label = _'Password (repeat)', 6.133 - name = 'password2', 6.134 - } 6.135 + if not member.authority == "ldap" then 6.136 + ui.tag{ 6.137 + tag = "p", 6.138 + content = _"Please choose a password and enter it twice. The password is case sensitive." 6.139 + } 6.140 + ui.field.text{ 6.141 + readonly = true, 6.142 + label = _'Login name', 6.143 + name = 'login', 6.144 + value = member.login 6.145 + } 6.146 + ui.field.password{ 6.147 + label = _'Password', 6.148 + name = 'password1', 6.149 + } 6.150 + ui.field.password{ 6.151 + label = _'Password (repeat)', 6.152 + name = 'password2', 6.153 + } 6.154 + end 6.155 + 6.156 ui.submit{ 6.157 text = _'activate account' 6.158 } 6.159 @@ -203,7 +235,10 @@ 6.160 ui.link{ 6.161 content = _"cancel registration", 6.162 module = "index", 6.163 - view = "index" 6.164 + action = "cancel_register", 6.165 + routing = { default = { 6.166 + mode = "redirect", module = "index", view = "index" 6.167 + } } 6.168 } 6.169 end ) 6.170 end
7.1 --- a/app/main/member/_action/update.lua Thu Jul 17 23:38:35 2014 +0200 7.2 +++ b/app/main/member/_action/update.lua Fri Jul 18 21:42:59 2014 +0200 7.3 @@ -17,12 +17,12 @@ 7.4 local update_args = { app.session.member } 7.5 7.6 for i, field in ipairs(fields) do 7.7 - if not config.locked_profile_fields[field] then 7.8 + if not util.is_profile_field_locked(app.session.member, field) then 7.9 param.update(app.session.member, field) 7.10 end 7.11 end 7.12 7.13 -if not config.locked_profile_fields.statement then 7.14 +if not util.is_profile_field_locked(app.session.member, "statement") then 7.15 local formatting_engine = param.get("formatting_engine") or config.enforce_formatting_engine 7.16 7.17 local formatting_engine_valid = false 7.18 @@ -47,7 +47,7 @@ 7.19 7.20 end 7.21 7.22 -if not config.locked_profile_fields.birthday then 7.23 +if not util.is_profile_field_locked(app.session.member, "birthday") then 7.24 if tostring(app.session.member.birthday) == "invalid_date" then 7.25 app.session.member.birthday = nil 7.26 slot.put_into("error", _"Date format is not valid. Please use following format: YYYY-MM-DD")
8.1 --- a/app/main/member/_action/update_email.lua Thu Jul 17 23:38:35 2014 +0200 8.2 +++ b/app/main/member/_action/update_email.lua Fri Jul 18 21:42:59 2014 +0200 8.3 @@ -1,6 +1,6 @@ 8.4 local resend = param.get("resend", atom.boolean) 8.5 8.6 -if not resend and config.locked_profile_fields.notify_email then 8.7 +if not resend and util.is_profile_field_locked(app.session.member, "notify_email") then 8.8 error("access denied") 8.9 end 8.10
9.1 --- a/app/main/member/_action/update_login.lua Thu Jul 17 23:38:35 2014 +0200 9.2 +++ b/app/main/member/_action/update_login.lua Fri Jul 18 21:42:59 2014 +0200 9.3 @@ -1,4 +1,4 @@ 9.4 -if config.locked_profile_fields.login then 9.5 +if util.is_profile_field_locked(app.session.member, "login") then 9.6 error("access denied") 9.7 end 9.8
10.1 --- a/app/main/member/_action/update_name.lua Thu Jul 17 23:38:35 2014 +0200 10.2 +++ b/app/main/member/_action/update_name.lua Fri Jul 18 21:42:59 2014 +0200 10.3 @@ -1,4 +1,4 @@ 10.4 -if config.locked_profile_fields.name then 10.5 +if util.is_profile_field_locked(app.session.member, "name") then 10.6 error("access denied") 10.7 end 10.8
11.1 --- a/app/main/member/_profile.lua Thu Jul 17 23:38:35 2014 +0200 11.2 +++ b/app/main/member/_profile.lua Fri Jul 18 21:42:59 2014 +0200 11.3 @@ -104,7 +104,7 @@ 11.4 if member.last_activity then 11.5 ui.field.text{ label = _"Last activity (updated daily)", value = format.date(member.last_activity) or _"not yet" } 11.6 end 11.7 - if member.statement and #member.statement > 0 then 11.8 + if member.id and member.statement and #member.statement > 0 then 11.9 slot.put("<br />") 11.10 slot.put("<br />") 11.11 ui.container{
12.1 --- a/app/main/member/_sidebar_whatcanido.lua Thu Jul 17 23:38:35 2014 +0200 12.2 +++ b/app/main/member/_sidebar_whatcanido.lua Fri Jul 18 21:42:59 2014 +0200 12.3 @@ -69,16 +69,18 @@ 12.4 local pages = {} 12.5 12.6 pages[#pages+1] = { view = "settings_notification", text = _"notification settings" } 12.7 - if not config.locked_profile_fields.notify_email then 12.8 + if not util.is_profile_field_locked(app.session.member, "notify_email") then 12.9 pages[#pages+1] = { view = "settings_email", text = _"change your notification email address" } 12.10 end 12.11 - if not config.locked_profile_fields.name then 12.12 + if not util.is_profile_field_locked(app.session.member, "name") then 12.13 pages[#pages+1] = { view = "settings_name", text = _"change your screen name" } 12.14 end 12.15 - if not config.locked_profile_fields.login then 12.16 + if not util.is_profile_field_locked(app.session.member, "login") then 12.17 pages[#pages+1] = { view = "settings_login", text = _"change your login" } 12.18 end 12.19 - pages[#pages+1] = { view = "settings_password", text = _"change your password" } 12.20 + if not util.is_profile_field_locked(app.session.member, "password") then 12.21 + pages[#pages+1] = { view = "settings_password", text = _"change your password" } 12.22 + end 12.23 pages[#pages+1] = { view = "developer_settings", text = _"developer settings" } 12.24 12.25 if config.download_dir then
13.1 --- a/config/example.lua Thu Jul 17 23:38:35 2014 +0200 13.2 +++ b/config/example.lua Fri Jul 18 21:42:59 2014 +0200 13.3 @@ -96,6 +96,14 @@ 13.4 -- Remove leading -- to use a option 13.5 -- ======================================================================== 13.6 13.7 +-- Disable registration 13.8 +-- ------------------------------------------------------------------------ 13.9 +-- Available options: 13.10 +-- false: registration is enabled (default) 13.11 +-- true: registration is disabled 13.12 +-- ------------------------------------------------------------------------ 13.13 +-- config.disable_registration = true 13.14 + 13.15 -- List of enabled languages, defaults to available languages 13.16 -- ------------------------------------------------------------------------ 13.17 -- config.enabled_languages = { 'en', 'de', 'eo', 'el', 'hu', 'it', 'nl', 'zh-Hans', 'zh-TW' }
14.1 --- a/config/init.lua Thu Jul 17 23:38:35 2014 +0200 14.2 +++ b/config/init.lua Fri Jul 18 21:42:59 2014 +0200 14.3 @@ -3,7 +3,7 @@ 14.4 -- (except when you really know what you are doing!) 14.5 -- ======================================================================== 14.6 14.7 -config.app_version = "3.0.2" 14.8 +config.app_version = "3.0.2+ldap" 14.9 14.10 if not config.password_hash_algorithm then 14.11 config.password_hash_algorithm = "crypt_sha512" 14.12 @@ -48,6 +48,10 @@ 14.13 config.check_delegations_default = "confirm" 14.14 end 14.15 14.16 +if config.ldap == nil then 14.17 + config.ldap = {} 14.18 +end 14.19 + 14.20 if not config.database then 14.21 config.database = { engine='postgresql', dbname='liquid_feedback' } 14.22 end
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 15.2 +++ b/env/encode/pg_hstore.lua Fri Jul 18 21:42:59 2014 +0200 15.3 @@ -0,0 +1,16 @@ 15.4 +-- Encodes a Lua table as PostgreSQL hstore text input 15.5 +-- TODO This should be implemented in the SQL abstraction layer 15.6 + 15.7 +function encode.pg_hstore(hstore_values) 15.8 + 15.9 + local entries = {} 15.10 + 15.11 + for key, val in pairs(hstore_values) do 15.12 + local escaped_key = encode.pg_hstore_value(key) 15.13 + local escaped_val = encode.pg_hstore_value(val) 15.14 + entries[#entries+1] = escaped_key .. "=>" .. escaped_val 15.15 + end 15.16 + 15.17 + return table.concat(entries, ", ") 15.18 + 15.19 +end
16.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 16.2 +++ b/env/encode/pg_hstore_value.lua Fri Jul 18 21:42:59 2014 +0200 16.3 @@ -0,0 +1,6 @@ 16.4 +-- Formats a value (or a key) for usage in the text representation of 16.5 +-- hstore fields 16.6 + 16.7 +function encode.pg_hstore_value(value) 16.8 + return '"' .. string.gsub(value, '([\\"])', "\\%1") .. '"' 16.9 +end 16.10 \ No newline at end of file
17.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 17.2 +++ b/env/ldap/__init.lua Fri Jul 18 21:42:59 2014 +0200 17.3 @@ -0,0 +1,2 @@ 17.4 +-- Lua library path for C modules for mldap 17.5 +package.cpath = request.get_app_basepath() .. "/lib/mldap/?.so" .. ";" .. package.cpath 17.6 \ No newline at end of file
18.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 18.2 +++ b/env/ldap/bind.lua Fri Jul 18 21:42:59 2014 +0200 18.3 @@ -0,0 +1,55 @@ 18.4 +-- binds to configured LDAP server 18.5 +-- -------------------------------------------------------------------------- 18.6 +-- omit arguments for anonymous bind 18.7 +-- 18.8 +-- arguments: 18.9 +-- dn: the distinguished name to be used fo binding (string) 18.10 +-- password: password credentials (string) 18.11 +-- 18.12 +-- returns: 18.13 +-- ldap: in case of success, an LDAP connection handle 18.14 +-- err: in case of an error, an error code (string) 18.15 +-- err2: error dependent extra error information 18.16 + 18.17 +function ldap.bind(dn, password) 18.18 + 18.19 + local libldap = require("mldap") 18.20 + 18.21 + local hostlist = ldap.get_hosts() 18.22 + 18.23 + -- try binding to LDAP server until success of no host entry left 18.24 + local ldap 18.25 + while not ldap do 18.26 + 18.27 + if #hostlist < 1 then 18.28 + break 18.29 + end 18.30 + 18.31 + local host = table.remove(hostlist, 1) 18.32 + 18.33 + local err 18.34 + ldap, err, errno = libldap.bind{ 18.35 + uri = host.uri, 18.36 + timeout = host.timeout, 18.37 + who = dn, 18.38 + password = password 18.39 + } 18.40 + 18.41 + if not err and ldap then 18.42 + return ldap, nil 18.43 + end 18.44 + 18.45 + local errno_string 18.46 + 18.47 + if errno then 18.48 + errno_string = libldap.errorcodes[errno] 18.49 + end 18.50 + 18.51 + if errno == libldap.errorcodes.invalid_credentials then 18.52 + return nil, "invalid_credentials", errno_string 18.53 + end 18.54 + end 18.55 + 18.56 + return nil, "cant_contact_ldap_server" 18.57 + 18.58 +end
19.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 19.2 +++ b/env/ldap/bind_as_app.lua Fri Jul 18 21:42:59 2014 +0200 19.3 @@ -0,0 +1,21 @@ 19.4 +-- binds to configured LDAP server with application's credentials 19.5 +-- -------------------------------------------------------------------------- 19.6 +-- 19.7 +-- returns 19.8 +-- ldap_conn: in case of success, an LDAP connection handle 19.9 +-- err: in case of an error, an error code (string) 19.10 + 19.11 +function ldap.bind_as_app() 19.12 + 19.13 + local dn, password 19.14 + 19.15 + if config.ldap.bind_as then 19.16 + dn = config.ldap.bind_as.dn 19.17 + password = config.ldap.bind_as.password 19.18 + end 19.19 + 19.20 + local ldap_conn, err = ldap.bind(dn, password) 19.21 + 19.22 + return ldap_conn, err 19.23 + 19.24 +end
20.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 20.2 +++ b/env/ldap/check_credentials.lua Fri Jul 18 21:42:59 2014 +0200 20.3 @@ -0,0 +1,47 @@ 20.4 +-- check if credentials (given by a user) are valid to bind to LDAP 20.5 +-- -------------------------------------------------------------------------- 20.6 +-- 20.7 +-- arguments: 20.8 +-- dn: The distinguished name to be used fo binding (string, required) 20.9 +-- password: Password credentials (string, required) 20.10 +-- 20.11 +-- returns 20.12 +-- success: true in cases of valid credentials 20.13 +-- false in cases of invalid credentials 20.14 +-- nil in undetermined cases, i.e. unavailable LDAP server 20.15 +-- err: error code in case of errors, otherwise nil (string) 20.16 +-- err2: error dependent extra error information 20.17 + 20.18 +function ldap.check_credentials(login, password) 20.19 + 20.20 + local filter = config.ldap.member.login_filter_map(login) 20.21 + local ldap_entry, err, err2 = ldap.get_member_entry(filter) 20.22 + 20.23 + if err == "too_many_entries_found" then 20.24 + return false, "invalid_credentials" 20.25 + end 20.26 + 20.27 + if err then 20.28 + return nil, err 20.29 + end 20.30 + if not ldap_entry then 20.31 + return false, "invalid_credentials" 20.32 + end 20.33 + 20.34 + local dn = ldap_entry.dn 20.35 + 20.36 + local ldap, err, err2 = ldap.bind(dn, password) 20.37 + 20.38 + if err == "invalid_credentials" then 20.39 + return false, "invalid_credentials" 20.40 + end 20.41 + 20.42 + if err then 20.43 + return nil, err, err2 20.44 + end 20.45 + 20.46 + ldap:unbind() 20.47 + 20.48 + return ldap_entry 20.49 + 20.50 +end
21.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 21.2 +++ b/env/ldap/create_member.lua Fri Jul 18 21:42:59 2014 +0200 21.3 @@ -0,0 +1,39 @@ 21.4 +-- Create a new member object from LDAP for an uid 21.5 +-- -------------------------------------------------------------------------- 21.6 +-- 21.7 +-- arguments: 21.8 +-- uid: uid of the new member object (required) 21.9 +-- 21.10 +-- returns: 21.11 +-- member: a LiquidFeedback Member object (in case of success) 21.12 +-- err: error code in case of an error (string) 21.13 +-- err2: error dependent extra error information 21.14 + 21.15 +function ldap.create_member(uid) 21.16 + 21.17 + local member = Member:new() 21.18 + 21.19 + member.authority = "ldap" 21.20 + 21.21 + member.authority_data = encode.pg_hstore{ 21.22 + uid = uid 21.23 + } 21.24 + 21.25 + local ldap_conn, ldap_entry, err, err2 = ldap.update_member_attr(member, nil, uid) 21.26 + 21.27 + if ldap_conn then 21.28 + ldap_conn:unbind() 21.29 + end 21.30 + 21.31 + member.authority_data = encode.pg_hstore{ 21.32 + uid = uid, 21.33 + login = config.ldap.member.login_map(ldap_entry) 21.34 + } 21.35 + 21.36 + if not err then 21.37 + return member 21.38 + end 21.39 + 21.40 + return nil, err, err2 21.41 + 21.42 +end
22.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 22.2 +++ b/env/ldap/escape_filter.lua Fri Jul 18 21:42:59 2014 +0200 22.3 @@ -0,0 +1,20 @@ 22.4 +-- Escape a string to be used as safe LDAP filter 22.5 +-- -------------------------------------------------------------------------- 22.6 +-- 22.7 +-- arguments: 22.8 +-- filter: the string to be escaped (required) 22.9 +-- 22.10 +-- returns: 22.11 +-- escaped_filter: the escaped result 22.12 + 22.13 +function ldap.escape_filter(filter) 22.14 + 22.15 + local null_pattern = (_VERSION == "Lua 5.1") and "%z" or "\000" 22.16 + 22.17 + return string.gsub(filter, "[\\%*%(%)\128-\255" .. null_pattern .. "]", function (char) 22.18 + 22.19 + return string.format("%02x", string.byte(char)) 22.20 + 22.21 + end) 22.22 + 22.23 +end 22.24 \ No newline at end of file
23.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 23.2 +++ b/env/ldap/get_hosts.lua Fri Jul 18 21:42:59 2014 +0200 23.3 @@ -0,0 +1,46 @@ 23.4 +-- generate a ready-to-use list of the configures LDAP servers 23.5 +-- -------------------------------------------------------------------------- 23.6 +-- result is in order of preference, including a round robin style random 23.7 +-- selection of the preference of hosts with the same preference 23.8 +-- 23.9 +-- returns: 23.10 +-- hostlist: flattened list of LDAP servers in order of preference 23.11 + 23.12 +function ldap.get_hosts() 23.13 + 23.14 + math.randomseed(os.time()) 23.15 + 23.16 + local hostlist = {} 23.17 + 23.18 + -- iterate through all entries on base level 23.19 + for i, host in ipairs (config.ldap.hosts) do 23.20 + 23.21 + -- a single host entry 23.22 + if host.uri then 23.23 + hostlist[#hostlist+1] = host 23.24 + 23.25 + -- or a list of host entries 23.26 + else 23.27 + local subhostlist = {} 23.28 + 23.29 + -- sort list of host entries by random 23.30 + for j, subhost in ipairs(host) do 23.31 + subhost.priority = math.random() 23.32 + subhostlist[#subhostlist+1] = subhost 23.33 + end 23.34 + table.sort(subhostlist, function (a,b) 23.35 + return a.priority < b.priority 23.36 + end) 23.37 + 23.38 + -- and add them to the main list 23.39 + for i, subhost in ipairs(subhostlist) do 23.40 + hostlist[#hostlist+1] = subhost 23.41 + end 23.42 + 23.43 + end 23.44 + 23.45 + end 23.46 + 23.47 + return hostlist 23.48 + 23.49 +end
24.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 24.2 +++ b/env/ldap/get_member_entry.lua Fri Jul 18 21:42:59 2014 +0200 24.3 @@ -0,0 +1,52 @@ 24.4 +-- gets the corresponding ldap entry for a given member login 24.5 +-- -------------------------------------------------------------------------- 24.6 +-- 24.7 +-- arguments: 24.8 +-- filter: the LDAP filter for searching the member (required) 24.9 +-- use_ldap_conn: an already existing LDAP connection to be used (optional) 24.10 +-- 24.11 +-- returns: 24.12 +-- ldap_entry: in case of success, the LDAP entry (object) 24.13 +-- err: in case of an error, an error message (string) 24.14 +-- err2: error dependent extra error information 24.15 + 24.16 +function ldap.get_member_entry(filter, use_ldap_conn) 24.17 + 24.18 + local ldap_conn, err 24.19 + 24.20 + if use_ldap_conn then 24.21 + ldap_conn = use_ldap_conn 24.22 + else 24.23 + ldap_conn, bind_err = ldap.bind_as_app() 24.24 + end 24.25 + 24.26 + if not ldap_conn then 24.27 + return nil, "ldap_bind_error", bind_err 24.28 + end 24.29 + 24.30 + local entries, search_err = ldap_conn:search{ 24.31 + base = config.ldap.base, 24.32 + scope = config.ldap.member.scope, 24.33 + filter = filter, 24.34 + attr = config.ldap.member.fetch_attr, 24.35 + } 24.36 + 24.37 + if not use_ldap_conn then 24.38 + ldap_conn:unbind() 24.39 + end 24.40 + 24.41 + if not entries then 24.42 + return nil, "ldap_search_error", search_err 24.43 + end 24.44 + 24.45 + if #entries > 1 then 24.46 + return nil, "too_many_ldap_entries_found" 24.47 + end 24.48 + 24.49 + if #entries < 0 then 24.50 + return nil, "no_ldap_entry_found" 24.51 + end 24.52 + 24.53 + return entries[1] 24.54 + 24.55 +end
25.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 25.2 +++ b/env/ldap/test.lua Fri Jul 18 21:42:59 2014 +0200 25.3 @@ -0,0 +1,141 @@ 25.4 +-- Part of test case for ldap4lqfb 25.5 + 25.6 +function ldap.test() 25.7 + 25.8 + local global_count = 0 25.9 + local global_failed = 0 25.10 + 25.11 + local function checkMember(uid, login, name, units_with_voting_priv, units_with_polling_priv) 25.12 + 25.13 + local failed = false 25.14 + 25.15 + local function testError(err, expected, got) 25.16 + failed = true 25.17 + global_failed = global_failed + 1 25.18 + if expected then 25.19 + print ("[" .. uid .. "] FAILED: " .. err .. " (expected: '" .. expected .. "' but got '" .. tostring(got) .. "')") 25.20 + else 25.21 + print ("[" .. uid .. "] FAILED: " .. err) 25.22 + end 25.23 + end 25.24 + 25.25 + local function testOk(test, expected) 25.26 + if test then 25.27 + print ("[" .. uid .. "] " .. test .. " ok ('" .. expected .. "')") 25.28 + else 25.29 + print ("[" .. uid .. "] success") 25.30 + end 25.31 + end 25.32 + 25.33 + local function test(field, expected, value) 25.34 + global_count = global_count + 1 25.35 + if expected ~= value then 25.36 + testError(field, expected, value) 25.37 + return 25.38 + end 25.39 + testOk(field, expected) 25.40 + end 25.41 + 25.42 + local members = Member:new_selector() 25.43 + :add_field{ "authority_data->'login' as authority_data_login" } 25.44 + :add_where{ "authority = ? AND authority_data->'uid' = ?", "ldap", uid } 25.45 + :exec() 25.46 + 25.47 + if #members < 1 then 25.48 + testError("Member not found in DB") 25.49 + return 25.50 + end 25.51 + 25.52 + if #members > 1 then 25.53 + testError("Found more than one DB entry") 25.54 + return 25.55 + end 25.56 + 25.57 + local member = members[1] 25.58 + 25.59 + if login == nil then 25.60 + if not member.locked then 25.61 + testError("Member not locked") 25.62 + else 25.63 + testOk("Member is locked", "true") 25.64 + end 25.65 + return 25.66 + end 25.67 + 25.68 + test("login", login, member.authority_data_login) 25.69 + test("name", name, member.name) 25.70 + 25.71 + for i, unit_id in ipairs(units_with_voting_priv) do 25.72 + global_count = global_count + 1 25.73 + local privilege = Privilege:by_pk(unit_id, member.id) 25.74 + if privilege and privilege.voting_right then 25.75 + testOk("voting_right", unit_id) 25.76 + else 25.77 + testError("voting_right", unit_id, "") 25.78 + end 25.79 + end 25.80 + 25.81 + local privileges_selector = Privilege:new_selector() 25.82 + :add_where{ "member_id = ?", member.id } 25.83 + :add_where("voting_right = true") 25.84 + if #units_with_voting_priv > 0 then 25.85 + local privileges = privileges_selector:add_where{ "unit_id NOT IN ($)", units_with_voting_priv } 25.86 + end 25.87 + local privileges = privileges_selector:exec() 25.88 + 25.89 + if #privileges > 0 then 25.90 + testError("voting_right", "count: 0", "count: " .. #privileges) 25.91 + else 25.92 + testOk("voting_right", "count: 0") 25.93 + end 25.94 + 25.95 + for i, unit_id in ipairs(units_with_polling_priv) do 25.96 + global_count = global_count + 1 25.97 + local privilege = Privilege:by_pk(unit_id, member.id) 25.98 + if privilege and privilege.polling_right then 25.99 + testOk("polling_right", unit_id) 25.100 + else 25.101 + testError("polling_right", unit_id, "") 25.102 + end 25.103 + end 25.104 + 25.105 + local privileges_selector = Privilege:new_selector() 25.106 + :add_where{ "member_id = ?", member.id } 25.107 + :add_where("polling_right = true") 25.108 + if #units_with_polling_priv > 0 then 25.109 + privileges_selector:add_where{ "unit_id NOT IN ($)", units_with_polling_priv } 25.110 + end 25.111 + local privileges = privileges_selector:exec() 25.112 + 25.113 + if #privileges > 0 then 25.114 + testError("polling_right", "count: " .. #units_with_polling_priv, "count: " .. #privileges) 25.115 + else 25.116 + testOk("polling_right", "count: " .. #units_with_polling_priv) 25.117 + end 25.118 + 25.119 + if not failed then 25.120 + return true 25.121 + end 25.122 + 25.123 + return false 25.124 + 25.125 + end 25.126 + 25.127 + checkMember("1000", "alice", "Alice Amberg", { 1 }, { }) 25.128 + checkMember("1001", "bob", "Bob Bobbersen", { 1, 2 }, { 1, 3 }) 25.129 + checkMember("1002", "chris", "Chris Carv", { 1, 2 }, { 1, 3 }) 25.130 + checkMember("1003", "daisy", "Daisy Duck", { 3 }, { 1, 2 }) 25.131 + checkMember("1004", "ernst", "Ernst Ernst", { 3 }, { 1, 2 }) 25.132 + checkMember("1005", "fredi", "Frederike Frei", { 1 }, { }) 25.133 + checkMember("1006") 25.134 + checkMember("1007", "helen", "Helen Hofstatter", { 1, 2 }, { 1, 3 }) 25.135 + checkMember("1008", "iwan", "Iwan Iwanowski", { 1 }, { }) 25.136 + checkMember("1009", "jasmina", "Jasmina Jasik", { 1 }, { }) 25.137 + 25.138 + print() 25.139 + 25.140 + local success_count = global_count - global_failed 25.141 + 25.142 + print (success_count .. " of " .. global_count .. " tests succeeded.") 25.143 + 25.144 +end
26.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 26.2 +++ b/env/ldap/update_all_members.lua Fri Jul 18 21:42:59 2014 +0200 26.3 @@ -0,0 +1,68 @@ 26.4 +-- check for all LiquidFeedback Members with LDAP authentication 26.5 +-- if the corresponding LDAP entry is still existent and updates 26.6 +-- changed attributes 26.7 +-- -------------------------------------------------------------------------- 26.8 +-- prints debug output to stdout 26.9 +-- 26.10 +-- returns 26.11 +-- success: true if no error occured during run 26.12 +-- false if at least one error occured during run 26.13 + 26.14 +function ldap.update_all_members() 26.15 + 26.16 + local some_error_occured = false 26.17 + 26.18 + local ldap_conn = ldap.bind_as_app() 26.19 + 26.20 + function update_member(member) 26.21 + 26.22 + local function failure (err, err2) 26.23 + Member.get_db_conn():query("ROLLBACK") 26.24 + io.stdout:write("ERROR: ", err, " (", err2, ") id=", tostring(member.id), " uid=", tostring(member.authority_data_uid), "\n") 26.25 + some_error_occured = true 26.26 + end 26.27 + 26.28 + local function success () 26.29 + Member.get_db_conn():query("COMMIT") 26.30 + io.stdout:write("ok: id=", tostring(member.id), " uid=", tostring(member.authority_data_uid), "\n") 26.31 + end 26.32 + 26.33 + Member.get_db_conn():query("BEGIN") 26.34 + 26.35 + local ldap_conn, ldap_entry, err, err2 = ldap.update_member_attr(member, ldap_conn) 26.36 + if err then 26.37 + failure("ldap_update_member", err) 26.38 + return 26.39 + end 26.40 + 26.41 + local err = member:try_save() 26.42 + if err then 26.43 + failure("member_try_save", err) 26.44 + return 26.45 + end 26.46 + 26.47 + if ldap_entry then 26.48 + local success, err, err2 = ldap.update_member_privileges(member, ldap_entry) 26.49 + if err then 26.50 + failure("ldap_update_member_privileges", err) 26.51 + return 26.52 + end 26.53 + 26.54 + end 26.55 + 26.56 + success() 26.57 + 26.58 + end 26.59 + 26.60 + 26.61 + local members = Member:get_all_by_authority("ldap") 26.62 + 26.63 + for i, member in ipairs(members) do 26.64 + update_member(member) 26.65 + end 26.66 + 26.67 + ldap_conn:unbind() 26.68 + 26.69 + return not some_error_occured 26.70 + 26.71 +end
27.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 27.2 +++ b/env/ldap/update_member_attr.lua Fri Jul 18 21:42:59 2014 +0200 27.3 @@ -0,0 +1,52 @@ 27.4 +-- check if the corresponding LDAP entry for an LiquidFeedback member 27.5 +-- object is still existent and updates changed attributes 27.6 +-- -------------------------------------------------------------------------- 27.7 +-- 27.8 +-- arguments: 27.9 +-- member: a LiquidFeedback Member object (required) 27.10 +-- ldap_conn: a ldap connection handle (optional) 27.11 +-- uid: the uid of the member (optional, required when creating members) 27.12 +-- 27.13 +-- returns: 27.14 +-- ldap_conn: an LDAP connection 27.15 +-- ldap_entry: the found LDAP entry (if any) 27.16 +-- err: error code in case of an error (string) 27.17 +-- err2: error dependent extra error information 27.18 +-- err3: error dependent extra error information 27.19 + 27.20 +function ldap.update_member_attr(member, ldap_conn, uid) 27.21 + 27.22 + -- do this only for members with ldap authentication 27.23 + if member.authority ~= "ldap" then 27.24 + return nil, nil, "member_is_not_authenticated_by_ldap" 27.25 + end 27.26 + 27.27 + local filter = config.ldap.member.uid_filter_map(member.authority_data_uid or uid) 27.28 + local ldap_entry, err, err2 = ldap.get_member_entry(filter, ldap_conn) 27.29 + 27.30 + if err then 27.31 + return ldap_conn, nil, "ldap_error", err, err2 27.32 + end 27.33 + 27.34 + -- If no corresponding entry found, lock the member 27.35 + if not ldap_entry then 27.36 + member.locked = true 27.37 + member.active = false 27.38 + return ldap_conn 27.39 + end 27.40 + 27.41 + -- If exactly one corresponding entry found, update the attributes 27.42 + local err = config.ldap.member.attr_map(ldap_entry, member) 27.43 + 27.44 + member.authority_data = encode.pg_hstore{ 27.45 + uid = member.authority_data_uid or uid, 27.46 + login = config.ldap.member.login_map(ldap_entry) 27.47 + } 27.48 + 27.49 + if err then 27.50 + return ldap_conn, ldap_entry, "attr_map_error", err 27.51 + end 27.52 + 27.53 + return ldap_conn, ldap_entry 27.54 + 27.55 +end
28.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 28.2 +++ b/env/ldap/update_member_privileges.lua Fri Jul 18 21:42:59 2014 +0200 28.3 @@ -0,0 +1,56 @@ 28.4 +-- Update member privileges from LDAP 28.5 +-- -------------------------------------------------------------------------- 28.6 +-- 28.7 +-- arguments: 28.8 +-- member: the member for which the privileges should be updated 28.9 +-- ldap_entry: the ldap entry to be used for updating the privileges 28.10 +-- 28.11 +-- returns: 28.12 +-- err: an error code, if an error occured (string) 28.13 +-- err2: Error dependent extra error information 28.14 + 28.15 +function ldap.update_member_privileges(member, ldap_entry) 28.16 + 28.17 + local privileges, err = config.ldap.member.privilege_map(ldap_entry, member) 28.18 + 28.19 + if err then 28.20 + return false, "privilege_map_error", err 28.21 + end 28.22 + 28.23 + local privileges_by_unit_id = {} 28.24 + for i, privilege in ipairs(privileges) do 28.25 + privileges_by_unit_id[privilege.unit_id] = privilege 28.26 + end 28.27 + 28.28 + local current_privileges = Privilege:by_member_id(member.id) 28.29 + local current_privilege_ids = {} 28.30 + 28.31 + for i, privilege in ipairs(current_privileges) do 28.32 + if privileges_by_unit_id[privilege.unit_id] then 28.33 + current_privilege_ids[privilege.unit_id] = privilege 28.34 + else 28.35 + privilege:destroy() 28.36 + end 28.37 + end 28.38 + 28.39 + for i, privilege in ipairs(privileges) do 28.40 + local current_privilege = current_privilege_ids[privilege.unit_id] 28.41 + if not current_privilege then 28.42 + current_privilege = Privilege:new() 28.43 + current_privilege.member_id = member.id 28.44 + current_privileges[#current_privileges+1] = current_privilege 28.45 + end 28.46 + for key, val in pairs(privilege) do 28.47 + current_privilege[key] = val 28.48 + end 28.49 + end 28.50 + 28.51 + for i, privilege in ipairs(current_privileges) do 28.52 + local err = privilege:try_save() 28.53 + if err then 28.54 + return false, "privilege_save_error", err 28.55 + end 28.56 + end 28.57 + 28.58 + return true 28.59 +end
29.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 29.2 +++ b/env/util/is_profile_field_locked.lua Fri Jul 18 21:42:59 2014 +0200 29.3 @@ -0,0 +1,16 @@ 29.4 +function util.is_profile_field_locked(member, field_name) 29.5 + if member.authority == "ldap" then 29.6 + if config.ldap.member.locked_profile_fields and config.ldap.member.locked_profile_fields[field_name] 29.7 + or field_name == "login" 29.8 + or field_name == "password" 29.9 + then 29.10 + return true 29.11 + end 29.12 + end 29.13 + 29.14 + if config.locked_profile_fields[field_name] then 29.15 + return true 29.16 + end 29.17 + 29.18 + return false 29.19 +end 29.20 \ No newline at end of file
30.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 30.2 +++ b/lib/mldap/Makefile Fri Jul 18 21:42:59 2014 +0200 30.3 @@ -0,0 +1,8 @@ 30.4 +mldap.so: mldap.o 30.5 + ld -shared -L/usr/lib -o mldap.so mldap.o /usr/lib/libldap.so 30.6 + 30.7 +mldap.o: mldap.c 30.8 + gcc -g -c -fPIC -I/usr/include -I/usr/include/lua5.1 -Wall -o mldap.o mldap.c 30.9 + 30.10 +clean:: 30.11 + rm -f mldap.so mldap.o
31.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 31.2 +++ b/lib/mldap/convert_errorcodes.lua Fri Jul 18 21:42:59 2014 +0200 31.3 @@ -0,0 +1,7 @@ 31.4 +#!/usr/bin/env lua 31.5 +for line in io.lines() do 31.6 + local ident, code = line:match("^#define[ \t]+LDAP_([A-Z][A-Z_]*)[ \t]+([^ \t/]+)") 31.7 + if ident then 31.8 + io.stdout:write(' {"', ident:lower(), '", ', tonumber(code) or code, '},\n') 31.9 + end 31.10 +end
32.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 32.2 +++ b/lib/mldap/mldap.c Fri Jul 18 21:42:59 2014 +0200 32.3 @@ -0,0 +1,567 @@ 32.4 +/* 32.5 + * minimalistic Lua LDAP interface library 32.6 + * 32.7 + * The library does not set any global variable itself and must thus be 32.8 + * loaded with 32.9 + * 32.10 + * mldap = require("mldap") 32.11 + * 32.12 + * or a similar statement. 32.13 + * 32.14 + * The library provides two functions, conn = bind{...} and unbind(conn) 32.15 + * to connect to and disconnect from the LDAP server respectively. The 32.16 + * unbind function is also provided as a method of the connection userdata 32.17 + * object (see below). 32.18 + * 32.19 + * The arguments to the bind{...} function are documented in the source code 32.20 + * (see C function mldap_bind). In case of success, the bind function returns 32.21 + * a userdata object that provides two methods: query{...} and unbind(). In 32.22 + * case of error, the bind function returns nil as first return value, an 32.23 + * error string as second return value, and a numeric error code as third 32.24 + * return value. A positive error code is an LDAP resultCode, a negative 32.25 + * error code is an OpenLDAP API error code: 32.26 + * 32.27 + * connection, error_string, numeric_error_code = mldap.bind{...} 32.28 + * 32.29 + * For translating numeric error codes to an identifying (machine readable) 32.30 + * string identifier, the library provides in addition to the two functions 32.31 + * a table named 'errorcodes', for example: 32.32 + * 32.33 + * 49 == mldap.errorcodes["invalid_credentials"] 32.34 + * 32.35 + * and 32.36 + * 32.37 + * mldap.errorcodes[49] == "invalid_credentials" 32.38 + * 32.39 + * The arguments and return value of the query{...} method of the connection 32.40 + * userdata object are also documented in the source code below (see 32.41 + * C function mldap_query). Error conditions are reported the same way as the 32.42 + * bind{...} function does. 32.43 + * 32.44 + * To close the connection, either the unbind() function of the library or 32.45 + * the unbind() method can be called; it is allowed to call them multiple 32.46 + * times, and they are also invoked by the garbage collector. 32.47 + * 32.48 + */ 32.49 + 32.50 +// Lua header inclusions: 32.51 +#include <lua.h> 32.52 +#include <lauxlib.h> 32.53 + 32.54 +// OpenLDAP header inclusions: 32.55 +#include <ldap.h> 32.56 + 32.57 +// Standard C inclusions: 32.58 +#include <stdlib.h> 32.59 +#include <stdbool.h> 32.60 +#include <unistd.h> 32.61 + 32.62 +// Error code translation is included from separate C file: 32.63 +#include "mldap_errorcodes.c" 32.64 + 32.65 +// Provide compatibility with Lua 5.1: 32.66 +#if LUA_VERSION_NUM < 502 32.67 +#define luaL_newlib(L, t) lua_newtable((L)); luaL_register(L, NULL, t) 32.68 +#define lua_rawlen lua_objlen 32.69 +#define lua_len lua_objlen 32.70 +#define luaL_setmetatable(L, regkey) \ 32.71 + lua_getfield((L), LUA_REGISTRYINDEX, (regkey)); \ 32.72 + lua_setmetatable((L), -2); 32.73 +#endif 32.74 + 32.75 +// prefix for all Lua registry entries of this library: 32.76 +#define MLDAP_REGKEY "556aeaf3c864af2e_mldap_" 32.77 + 32.78 + 32.79 +static const char *mldap_get_named_string_arg( 32.80 + // gets a named argument of type "string" from a table at the given stack position 32.81 + 32.82 + lua_State *L, // pointer to lua_State variable 32.83 + int idx, // stack index of the table containing the named arguments 32.84 + const char *argname, // name of the argument 32.85 + int mandatory // if not 0, then the argument is mandatory and an error is raised if it isn't found 32.86 + 32.87 + // leaves the string as new element on top of the stack 32.88 +) { 32.89 + 32.90 + // pushes the table entry with the given argument name on top of the stack: 32.91 + lua_getfield(L, idx, argname); 32.92 + 32.93 + // check, if the entry is nil or false: 32.94 + if (!lua_toboolean(L, -1)) { 32.95 + 32.96 + // throw error, if named argument is mandatory: 32.97 + if (mandatory) return luaL_error(L, "Named argument '%s' missing", argname), NULL; 32.98 + 32.99 + // return NULL pointer, if named argument is not mandatory: 32.100 + return NULL; 32.101 + 32.102 + } 32.103 + 32.104 + // throw error, if the value of the argument is not string: 32.105 + if (lua_type(L, -1) != LUA_TSTRING) return luaL_error(L, "Named argument '%s' is not a string", argname), NULL; 32.106 + 32.107 + // return a pointer to the string, leaving the string on top of the stack to avoid garbage collection: 32.108 + return lua_tostring(L, -1); 32.109 + 32.110 + // leaves one element on the stack 32.111 +} 32.112 + 32.113 + 32.114 +static int mldap_get_named_number_arg( 32.115 + // gets a named argument of type "number" from a table at the given stack position 32.116 + 32.117 + lua_State *L, // pointer to lua_State variable 32.118 + int idx, // stack index of the table containing the named arguments 32.119 + const char *argname, // name of the argument 32.120 + int mandatory, // if not 0, then the argument is mandatory and an error is raised if it isn't found 32.121 + lua_Number default_value // default value to return, if the argument is not mandatory and nil or false 32.122 + 32.123 + // opposed to 'mldap_get_named_string_arg', this function leaves no element on the stack 32.124 +) { 32.125 + 32.126 + lua_Number value; // value to return 32.127 + 32.128 + // pushes the table entry with the given argument name on top of the stack: 32.129 + lua_getfield(L, idx, argname); 32.130 + 32.131 + // check, if the entry is nil or false: 32.132 + if (lua_isnil(L, -1)) { 32.133 + 32.134 + // throw error, if named argument is mandatory: 32.135 + if (mandatory) return luaL_error(L, "Named argument '%s' missing", argname), 0; 32.136 + 32.137 + // set default value as return value, if named argument is not mandatory: 32.138 + value = default_value; 32.139 + 32.140 + } else { 32.141 + 32.142 + // throw error, if the value of the argument is not a number: 32.143 + if (lua_type(L, -1) != LUA_TNUMBER) return luaL_error(L, "Named argument '%s' is not a number", argname), 0; 32.144 + 32.145 + // set return value to the number: 32.146 + value = lua_tonumber(L, -1); 32.147 + 32.148 + } 32.149 + 32.150 + // remove unnecessary element from stack (not needed to avoid garbage collection): 32.151 + return value; 32.152 + 32.153 + // leaves no new elements on the stack 32.154 +} 32.155 + 32.156 + 32.157 +static int mldap_scope( 32.158 + // converts a string ("base", "onelevel", "subtree", "children") to an integer representing the LDAP scope 32.159 + // and throws an error for any unknown string 32.160 + 32.161 + lua_State *L, // pointer to lua_State variable (needed to throw errors) 32.162 + const char *scope_string // string that is either ("base", "onelevel", "subtree", "children") 32.163 + 32.164 + // does not affect or use the Lua stack at all 32.165 +) { 32.166 + 32.167 + // return integer according to string value: 32.168 + if (!strcmp(scope_string, "base")) return LDAP_SCOPE_BASE; 32.169 + if (!strcmp(scope_string, "onelevel")) return LDAP_SCOPE_ONELEVEL; 32.170 + if (!strcmp(scope_string, "subtree")) return LDAP_SCOPE_SUBTREE; 32.171 + if (!strcmp(scope_string, "children")) return LDAP_SCOPE_CHILDREN; 32.172 + 32.173 + // throw error for unknown string values: 32.174 + return luaL_error(L, "Invalid LDAP scope: '%s'", scope_string), 0; 32.175 + 32.176 +} 32.177 + 32.178 + 32.179 +static int mldap_bind(lua_State *L) { 32.180 + // Lua C function that takes named arguments as a table 32.181 + // and returns a userdata object, representing the LDAP connection 32.182 + // or returns nil, an error string, and an error code (integer) on error 32.183 + 32.184 + // named arguments: 32.185 + // "uri" (string) server URI to connect to 32.186 + // "who" (string) DN to bind as 32.187 + // "password" (string) password for DN to bind as 32.188 + // "timeout" (number) timeout in seconds 32.189 + 32.190 + static const int ldap_version = LDAP_VERSION3; // providing a pointer (&ldap_version) to set LDAP protocol version 3 32.191 + const char *uri; // C string for "uri" argument 32.192 + const char *who; // C string for "who" argument 32.193 + struct berval cred; // credentials ("password") are stored as struct berval 32.194 + lua_Number timeout_float; // float (lua_Number) for timeout 32.195 + struct timeval timeout; // timeout is stored as struct timeval 32.196 + int ldap_error; // LDAP error code (as returned by libldap calls) 32.197 + LDAP *ldp; // pointer to an opaque OpenLDAP structure representing the connection 32.198 + LDAP **ldp_ptr; // pointer to a Lua userdata structure (that only contains the 'ldp' pointer) 32.199 + 32.200 + // throw error if first argument is not a table: 32.201 + if (lua_type(L, 1) != LUA_TTABLE) { 32.202 + luaL_error(L, "Argument to function 'bind' is not a table."); 32.203 + } 32.204 + 32.205 + // extract arguments: 32.206 + uri = mldap_get_named_string_arg(L, 1, "uri", true); 32.207 + who = mldap_get_named_string_arg(L, 1, "who", false); 32.208 + cred.bv_val = mldap_get_named_string_arg(L, 1, "password", false); 32.209 + if (cred.bv_val) cred.bv_len = strlen(cred.bv_val); 32.210 + else cred.bv_len = 0; 32.211 + timeout_float = mldap_get_named_number_arg(L, 1, "timeout", false, -1); 32.212 + timeout.tv_sec = timeout_float; 32.213 + timeout.tv_usec = (timeout_float - timeout.tv_sec) * 1000000; 32.214 + 32.215 + // initialize OpenLDAP structure and provide URI for connection: 32.216 + ldap_error = ldap_initialize(&ldp, uri); 32.217 + // on error, jump to label "mldap_queryconn_error1", as no ldap_unbind_ext_s() is needed: 32.218 + if (ldap_error != LDAP_SUCCESS) goto mldap_queryconn_error1; 32.219 + 32.220 + // set LDAP protocol version 3: 32.221 + ldap_error = ldap_set_option(ldp, LDAP_OPT_PROTOCOL_VERSION, &ldap_version); 32.222 + // on error, jump to label "mldap_queryconn_error2", as ldap_unbind_ext_s() must be called: 32.223 + if (ldap_error != LDAP_SUCCESS) goto mldap_queryconn_error2; 32.224 + 32.225 + // set timeout for asynchronous OpenLDAP library calls: 32.226 + ldap_error = ldap_set_option(ldp, LDAP_OPT_TIMEOUT, &timeout); 32.227 + // on error, jump to label "mldap_queryconn_error2", as ldap_unbind_ext_s() must be called: 32.228 + if (ldap_error != LDAP_SUCCESS) goto mldap_queryconn_error2; 32.229 + 32.230 + // connect to LDAP server: 32.231 + ldap_error = ldap_sasl_bind_s( 32.232 + ldp, // pointer to opaque OpenLDAP structure representing the connection 32.233 + who, // DN to bind as 32.234 + LDAP_SASL_SIMPLE, // SASL mechanism "simple" for password authentication 32.235 + &cred, // password as struct berval 32.236 + NULL, // no server controls 32.237 + NULL, // no client controls 32.238 + NULL // do not store server credentials 32.239 + ); 32.240 + 32.241 + // error handling: 32.242 + if (ldap_error != LDAP_SUCCESS) { 32.243 + 32.244 + // error label to jump to, if a call of ldap_unbind_ext_s() is required: 32.245 + mldap_queryconn_error2: 32.246 + 32.247 + // close connection and free resources: 32.248 + ldap_unbind_ext_s(ldp, NULL, NULL); 32.249 + 32.250 + // error label to jump to, if no call of ldap_unbind_ext_s() is required: 32.251 + mldap_queryconn_error1: 32.252 + 32.253 + // return three values: 32.254 + lua_pushnil(L); // return nil as first value 32.255 + lua_pushstring(L, ldap_err2string(ldap_error)); // return error string as second value 32.256 + lua_pushinteger(L, ldap_error); // return error code (integer) as third value 32.257 + return 3; 32.258 + 32.259 + } 32.260 + 32.261 + // create new Lua userdata object (that will contain the 'ldp' pointer) on top of stack: 32.262 + ldp_ptr = lua_newuserdata(L, sizeof(LDAP *)); 32.263 + 32.264 + // set metatable of Lua userdata object: 32.265 + luaL_setmetatable(L, MLDAP_REGKEY "connection_metatable"); 32.266 + 32.267 + // write contents of Lua userdata object (the 'ldp' pointer): 32.268 + *ldp_ptr = ldp; 32.269 + 32.270 + // return Lua userdata object from top of stack: 32.271 + return 1; 32.272 + 32.273 +} 32.274 + 32.275 + 32.276 +static int mldap_search(lua_State *L) { 32.277 + // Lua C function used as "search" method of Lua userdata object representing the LDAP connection 32.278 + // that takes a Lua userdata object (the LDAP connection) as first argument, 32.279 + // a table with named arguments as second argument, 32.280 + // and returns a result table on success (see below) 32.281 + // or returns nil, an error string, and an error code (integer) on error 32.282 + 32.283 + // named arguments: 32.284 + // "base" (string) DN of the entry at which to start the search 32.285 + // "scope" (string) scope of the search, one of: 32.286 + // "base" to search the object itself 32.287 + // "onelevel" to search the object's immediate children 32.288 + // "subtree" to search the object and all its descendants 32.289 + // "children" to search all of the descendants 32.290 + // "filter" (string) string representation of the filter to apply in the search 32.291 + // (conforming to RFC 4515 as extended in RFC 4526) 32.292 + // "attrs" (table) list of attribute descriptions (each a string) to return from matching entries 32.293 + 32.294 + // structure of result table: 32.295 + // { 32.296 + // { dn = <distinguished name (DN)>, 32.297 + // <attr1> = { <value1>, <value2>, ... }, 32.298 + // <attr2> = { <value1>, <value2>, ... }, 32.299 + // ... 32.300 + // }, 32.301 + // { dn = <distinguished name (DN)>, 32.302 + // <attr1> = { <value1>, <value2>, ... }, 32.303 + // <attr2> = { <value1>, <value2>, ... }, 32.304 + // ... 32.305 + // }, 32.306 + // ... 32.307 + // } 32.308 + 32.309 + const char *base; // C string for "base" argument 32.310 + const char *scope_string; // C string for "scope" argument 32.311 + int scope; // integer representing the scope 32.312 + const char *filter; // C string for "filter" argument 32.313 + size_t nattrs; // number of attributes in "attrs" argument 32.314 + char **attrs; // C string array of "attrs" argument 32.315 + size_t attr_idx; // index variable for building the C string array of "attrs" 32.316 + int ldap_error; // LDAP error code (as returned by libldap calls) 32.317 + LDAP **ldp_ptr; // pointer to a pointer to the OpenLDAP structure representing the connection 32.318 + LDAPMessage *res; // pointer to the result of ldap_search_ext_s() call 32.319 + LDAPMessage *ent; // pointer to an entry in the result of ldap_search_ext_s() call 32.320 + int i; // integer to fill the Lua table returned as result 32.321 + 32.322 + // truncate the Lua stack to 2 elements: 32.323 + lua_settop(L, 2); 32.324 + 32.325 + // check if the first argument is a Lua userdata object with the correct metatable 32.326 + // and get a C pointer to that userdata object: 32.327 + ldp_ptr = luaL_checkudata(L, 1, MLDAP_REGKEY "connection_metatable"); 32.328 + 32.329 + // throw an error, if the connection has already been closed: 32.330 + if (!*ldp_ptr) { 32.331 + return luaL_error(L, "LDAP connection has already been closed"); 32.332 + } 32.333 + 32.334 + // check if the second argument is a table, and throw an error if it's not a table: 32.335 + if (lua_type(L, 2) != LUA_TTABLE) { 32.336 + luaL_error(L, "Argument to function 'bind' is not a table."); 32.337 + } 32.338 + 32.339 + // extract named arguments (requires memory allocation for 'attrs'): 32.340 + base = mldap_get_named_string_arg(L, 2, "base", true); // pushed to 3 32.341 + scope_string = mldap_get_named_string_arg(L, 2, "scope", true); // pushed to 4 32.342 + scope = mldap_scope(L, scope_string); 32.343 + lua_pop(L, 1); // removes stack element 4 32.344 + filter = mldap_get_named_string_arg(L, 2, "filter", false); // pushed to 4 32.345 + lua_getfield(L, 2, "attrs"); // pushed to 5 32.346 + nattrs = lua_len(L, -1); 32.347 + attrs = calloc(nattrs + 1, sizeof(char *)); // memory allocation, +1 for terminating NULL 32.348 + if (!attrs) return luaL_error(L, "Memory allocation error in C function 'mldap_queryconn'"); 32.349 + for (attr_idx=0; attr_idx<nattrs; attr_idx++) { 32.350 + lua_pushinteger(L, attr_idx+1); 32.351 + lua_gettable(L, 5); // pushed onto variable stack position 32.352 + if (lua_type(L, -1) != LUA_TSTRING) { 32.353 + free(attrs); 32.354 + return luaL_error(L, "Element in attribute list is not a string"); 32.355 + } 32.356 + attrs[i] = lua_tostring(L, -1); 32.357 + } 32.358 + // attrs[nattrs] = NULL; // unnecessary due to calloc 32.359 + 32.360 + // execute LDAP search and store pointer to the result in 'res': 32.361 + ldap_error = ldap_search_ext_s( 32.362 + *ldp_ptr, // pointer to the opaque OpenLDAP structure representing the connection 32.363 + base, // DN of the entry at which to start the search 32.364 + scope, // scope of the search 32.365 + filter, // string representation of the filter to apply in the search 32.366 + attrs, // array of attribute descriptions (array of strings) 32.367 + 0, // do not only request attribute descriptions but also values 32.368 + NULL, // no server controls 32.369 + NULL, // no client controls 32.370 + NULL, // do not set another timeout 32.371 + 0, // no sizelimit 32.372 + &res // store result in 'res' 32.373 + ); 32.374 + 32.375 + // free data structures that have been allocated for the 'attrs' array: 32.376 + free(attrs); 32.377 + 32.378 + // return nil, an error string, and an error code (integer) in case of error: 32.379 + if (ldap_error != LDAP_SUCCESS) { 32.380 + lua_pushnil(L); 32.381 + lua_pushstring(L, ldap_err2string(ldap_error)); 32.382 + lua_pushinteger(L, ldap_error); 32.383 + return 3; 32.384 + } 32.385 + 32.386 + // clear Lua stack: 32.387 + lua_settop(L, 0); 32.388 + 32.389 + // create result table for all entries at stack position 1: 32.390 + lua_newtable(L); 32.391 + 32.392 + // use ldap_first_entry() and ldap_next_entry() functions 32.393 + // to iterate through all entries in the result 'res' 32.394 + // and count 'i' from 1 up: 32.395 + for ( 32.396 + ent=ldap_first_entry(*ldp_ptr, res), i=1; 32.397 + ent; 32.398 + ent=ldap_next_entry(*ldp_ptr, ent), i++ 32.399 + ) { 32.400 + 32.401 + char *attr; // name of attribute 32.402 + BerElement *ber; // structure to iterate through attributes 32.403 + char *dn; // LDAP entry name (distinguished name) 32.404 + 32.405 + // create result table for one entry at stack position 2: 32.406 + lua_newtable(L); 32.407 + 32.408 + // use ldap_first_attribute() and ldap_next_attribute() 32.409 + // as well as 'BerElement *ber' to iterate through all attributes 32.410 + // ('ber' must be free'd with ber_free(ber, 0) call later): 32.411 + for ( 32.412 + attr=ldap_first_attribute(*ldp_ptr, ent, &ber); 32.413 + attr; 32.414 + attr=ldap_next_attribute(*ldp_ptr, ent, ber) 32.415 + ) { 32.416 + 32.417 + struct berval **vals; // pointer to (first element of) array of values 32.418 + struct berval **val; // pointer to a value represented as 'struct berval' structure 32.419 + int j; // integer to fill a Lua table 32.420 + 32.421 + // push the attribute name to Lua stack position 3: 32.422 + lua_pushstring(L, attr); 32.423 + 32.424 + // create a new table for the attribute's values on Lua stack position 4: 32.425 + lua_newtable(L); 32.426 + 32.427 + // call ldap_get_values_len() to obtain the values 32.428 + // (required to be free'd later with ldap_value_free_len()): 32.429 + vals = ldap_get_values_len(*ldp_ptr, ent, attr); 32.430 + 32.431 + // iterate through values and count 'j' from 1 up: 32.432 + for (val=vals, j=1; *val; val++, j++) { 32.433 + 32.434 + // push value to Lua stack position 5: 32.435 + lua_pushlstring(L, (*val)->bv_val, (*val)->bv_len); 32.436 + 32.437 + // pop value from Lua stack position 5 32.438 + // and store it in table on Lua stack position 4: 32.439 + lua_rawseti(L, 4, j); 32.440 + 32.441 + } 32.442 + 32.443 + // free data structure of values: 32.444 + ldap_value_free_len(vals); 32.445 + 32.446 + // pop attribute name from Lua stack position 3 32.447 + // and pop value table from Lua stack position 4 32.448 + // and store them in result table for entry at Lua stack position 2: 32.449 + lua_settable(L, 2); 32.450 + 32.451 + } 32.452 + 32.453 + // free 'BerElement *ber' stucture that has been used to iterate through all attributes 32.454 + // (second argument is zero due to manpage of ldap_first_attribute()): 32.455 + ber_free(ber, 0); 32.456 + 32.457 + // store distinguished name (DN) on Lua stack position 3 32.458 + // (aquired memory is free'd with ldap_memfree()): 32.459 + dn = ldap_get_dn(*ldp_ptr, ent); 32.460 + lua_pushstring(L, dn); 32.461 + ldap_memfree(dn); 32.462 + 32.463 + // pop distinguished name (DN) from Lua stack position 3 32.464 + // and store it in field "dn" of entry result table at stack position 2 32.465 + lua_setfield(L, 2, "dn"); 32.466 + 32.467 + // pop entry result table from Lua stack position 2 32.468 + // and store it in table at stack position 1: 32.469 + lua_rawseti(L, 1, i); 32.470 + 32.471 + } 32.472 + 32.473 + // return result table from top of Lua stack (stack position 1): 32.474 + return 1; 32.475 + 32.476 +} 32.477 + 32.478 +static int mldap_unbind(lua_State *L) { 32.479 + // Lua C function used as "unbind" function of module and "unbind" method of Lua userdata object 32.480 + // closing the LDAP connection (if still open) 32.481 + // returning nothing 32.482 + 32.483 + LDAP **ldp_ptr; // pointer to a pointer to the OpenLDAP structure representing the connection 32.484 + 32.485 + // check if the first argument is a Lua userdata object with the correct metatable 32.486 + // and get a C pointer to that userdata object: 32.487 + ldp_ptr = luaL_checkudata(L, 1, MLDAP_REGKEY "connection_metatable"); 32.488 + 32.489 + // check if the Lua userdata object still contains a pointer: 32.490 + if (*ldp_ptr) { 32.491 + 32.492 + // if it does, then call ldap_unbind_ext_s(): 32.493 + ldap_unbind_ext_s( 32.494 + *ldp_ptr, // pointer to the opaque OpenLDAP structure representing the connection 32.495 + NULL, // no server controls 32.496 + NULL // no client controls 32.497 + ); 32.498 + 32.499 + // store NULL pointer in Lua userdata to mark connection as closed 32.500 + *ldp_ptr = NULL; 32.501 + } 32.502 + 32.503 + // returning nothing: 32.504 + return 0; 32.505 + 32.506 +} 32.507 + 32.508 + 32.509 +// registration information for library functions: 32.510 +static const struct luaL_Reg mldap_module_functions[] = { 32.511 + {"bind", mldap_bind}, 32.512 + {"unbind", mldap_unbind}, 32.513 + {NULL, NULL} 32.514 +}; 32.515 + 32.516 + 32.517 +// registration information for methods of connection object: 32.518 +static const struct luaL_Reg mldap_connection_methods[] = { 32.519 + {"search", mldap_search}, 32.520 + {"unbind", mldap_unbind}, 32.521 + {NULL, NULL} 32.522 +}; 32.523 + 32.524 + 32.525 +// registration information for connection metatable: 32.526 +static const struct luaL_Reg mldap_connection_metamethods[] = { 32.527 + {"__gc", mldap_unbind}, 32.528 + {NULL, NULL} 32.529 +}; 32.530 + 32.531 + 32.532 +// luaopen function to initialize/register library: 32.533 +int luaopen_mldap(lua_State *L) { 32.534 + 32.535 + // clear Lua stack: 32.536 + lua_settop(L, 0); 32.537 + 32.538 + // create table with library functions on Lua stack position 1: 32.539 + luaL_newlib(L, mldap_module_functions); 32.540 + 32.541 + // create metatable for connection objects on Lua stack position 2: 32.542 + luaL_newlib(L, mldap_connection_metamethods); 32.543 + 32.544 + // create table with methods for connection object on Lua stack position 3: 32.545 + luaL_newlib(L, mldap_connection_methods); 32.546 + 32.547 + // pop table with methods for connection object from Lua stack position 3 32.548 + // and store it as "__index" in metatable: 32.549 + lua_setfield(L, 2, "__index"); 32.550 + 32.551 + // pop table with metatable for connection objects from Lua stack position 2 32.552 + // and store it in the Lua registry: 32.553 + lua_setfield(L, LUA_REGISTRYINDEX, MLDAP_REGKEY "connection_metatable"); 32.554 + 32.555 + // create table for error code mappings on Lua stack position 2: 32.556 + lua_newtable(L); 32.557 + 32.558 + // fill table for error code mappings 32.559 + // that maps integer error codes to error code strings 32.560 + // and vice versa: 32.561 + mldap_set_errorcodes(L); 32.562 + 32.563 + // pop table for error code mappings from Lua stack position 2 32.564 + // and store it as "errorcodes" in table with library functions: 32.565 + lua_setfield(L, 1, "errorcodes"); 32.566 + 32.567 + // return table with library functions from top of Lua stack: 32.568 + return 1; 32.569 + 32.570 +}
33.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 33.2 +++ b/lib/mldap/mldap_errorcodes.c Fri Jul 18 21:42:59 2014 +0200 33.3 @@ -0,0 +1,199 @@ 33.4 +/* This file contains error code mappings of LDAP error codes and OpenLDAP 33.5 + * error codes. 33.6 + * 33.7 + * The collection of error codes (mldap_errorcodes[]) has been derived from 33.8 + * the file ldap.h that is part of OpenLDAP Software. OpenLDAP's license 33.9 + * information is stated below: 33.10 + * 33.11 + * This work is part of OpenLDAP Software <http://www.openldap.org/>. 33.12 + * 33.13 + * Copyright 1998-2013 The OpenLDAP Foundation. 33.14 + * All rights reserved. 33.15 + * 33.16 + * Redistribution and use in source and binary forms, with or without 33.17 + * modification, are permitted only as authorized by the OpenLDAP 33.18 + * Public License. 33.19 + * 33.20 + * A copy of this license is available below: 33.21 + * 33.22 + * The OpenLDAP Public License 33.23 + * Version 2.8, 17 August 2003 33.24 + * 33.25 + * Redistribution and use of this software and associated documentation 33.26 + * ("Software"), with or without modification, are permitted provided 33.27 + * that the following conditions are met: 33.28 + * 33.29 + * 1. Redistributions in source form must retain copyright statements 33.30 + * and notices, 33.31 + * 33.32 + * 2. Redistributions in binary form must reproduce applicable copyright 33.33 + * statements and notices, this list of conditions, and the following 33.34 + * disclaimer in the documentation and/or other materials provided 33.35 + * with the distribution, and 33.36 + * 33.37 + * 3. Redistributions must contain a verbatim copy of this document. 33.38 + * 33.39 + * The OpenLDAP Foundation may revise this license from time to time. 33.40 + * Each revision is distinguished by a version number. You may use 33.41 + * this Software under terms of this license revision or under the 33.42 + * terms of any subsequent revision of the license. 33.43 + * 33.44 + * THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS 33.45 + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, 33.46 + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 33.47 + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 33.48 + * SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S) 33.49 + * OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 33.50 + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 33.51 + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 33.52 + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33.53 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33.54 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 33.55 + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33.56 + * POSSIBILITY OF SUCH DAMAGE. 33.57 + * 33.58 + * The names of the authors and copyright holders must not be used in 33.59 + * advertising or otherwise to promote the sale, use or other dealing 33.60 + * in this Software without specific, written prior permission. Title 33.61 + * to copyright in this Software shall at all times remain with copyright 33.62 + * holders. 33.63 + * 33.64 + * OpenLDAP is a registered trademark of the OpenLDAP Foundation. 33.65 + * 33.66 + * Copyright 1999-2003 The OpenLDAP Foundation, Redwood City, 33.67 + * California, USA. All Rights Reserved. Permission to copy and 33.68 + * distribute verbatim copies of this document is granted. 33.69 + * 33.70 + * End of OpenLDAP Public License 33.71 + * 33.72 + * Portions Copyright (c) 1990 Regents of the University of Michigan. 33.73 + * All rights reserved. 33.74 + * 33.75 + * Redistribution and use in source and binary forms are permitted 33.76 + * provided that this notice is preserved and that due credit is given 33.77 + * to the University of Michigan at Ann Arbor. The name of the University 33.78 + * may not be used to endorse or promote products derived from this 33.79 + * software without specific prior written permission. This software 33.80 + * is provided ``as is'' without express or implied warranty. 33.81 + * 33.82 + * End of OpenLDAP's license information 33.83 + */ 33.84 + 33.85 +// type for entry in mldap_errorcodes[] array: 33.86 +struct mldap_errorcode { 33.87 + const char *ident; 33.88 + int code; 33.89 +}; 33.90 + 33.91 +// NULL terminated array of error code strings with error code integers 33.92 +// derived from ldap.h (see above copyright notice): 33.93 +static const struct mldap_errorcode mldap_errorcodes[] = { 33.94 + {"operations_error", 1}, 33.95 + {"protocol_error", 2}, 33.96 + {"timelimit_exceeded", 3}, 33.97 + {"sizelimit_exceeded", 4}, 33.98 + {"compare_false", 5}, 33.99 + {"compare_true", 6}, 33.100 +// {"auth_method_not_supported", 7}, 33.101 + {"strong_auth_not_supported", 7}, 33.102 + {"strong_auth_required", 8}, 33.103 +// {"stronger_auth_required", 8}, 33.104 + {"partial_results", 9}, 33.105 + {"referral", 10}, 33.106 + {"adminlimit_exceeded", 11}, 33.107 + {"unavailable_critical_extension", 12}, 33.108 + {"confidentiality_required", 13}, 33.109 + {"sasl_bind_in_progress", 14}, 33.110 + {"no_such_attribute", 16}, 33.111 + {"undefined_type", 17}, 33.112 + {"inappropriate_matching", 18}, 33.113 + {"constraint_violation", 19}, 33.114 + {"type_or_value_exists", 20}, 33.115 + {"invalid_syntax", 21}, 33.116 + {"no_such_object", 32}, 33.117 + {"alias_problem", 33}, 33.118 + {"invalid_dn_syntax", 34}, 33.119 + {"is_leaf", 35}, 33.120 + {"alias_deref_problem", 36}, 33.121 + {"x_proxy_authz_failure", 47}, 33.122 + {"inappropriate_auth", 48}, 33.123 + {"invalid_credentials", 49}, 33.124 + {"insufficient_access", 50}, 33.125 + {"busy", 51}, 33.126 + {"unavailable", 52}, 33.127 + {"unwilling_to_perform", 53}, 33.128 + {"loop_detect", 54}, 33.129 + {"naming_violation", 64}, 33.130 + {"object_class_violation", 65}, 33.131 + {"not_allowed_on_nonleaf", 66}, 33.132 + {"not_allowed_on_rdn", 67}, 33.133 + {"already_exists", 68}, 33.134 + {"no_object_class_mods", 69}, 33.135 + {"results_too_large", 70}, 33.136 + {"affects_multiple_dsas", 71}, 33.137 + {"vlv_error", 76}, 33.138 + {"other", 80}, 33.139 + {"cup_resources_exhausted", 113}, 33.140 + {"cup_security_violation", 114}, 33.141 + {"cup_invalid_data", 115}, 33.142 + {"cup_unsupported_scheme", 116}, 33.143 + {"cup_reload_required", 117}, 33.144 + {"cancelled", 118}, 33.145 + {"no_such_operation", 119}, 33.146 + {"too_late", 120}, 33.147 + {"cannot_cancel", 121}, 33.148 + {"assertion_failed", 122}, 33.149 + {"proxied_authorization_denied", 123}, 33.150 + {"sync_refresh_required", 4096}, 33.151 + {"x_sync_refresh_required", 16640}, 33.152 + {"x_assertion_failed", 16655}, 33.153 + {"x_no_operation", 16654}, 33.154 + {"x_no_referrals_found", 16656}, 33.155 + {"x_cannot_chain", 16657}, 33.156 + {"x_invalidreference", 16658}, 33.157 + {"x_txn_specify_okay", 16672}, 33.158 + {"x_txn_id_invalid", 16673}, 33.159 + {"server_down", (-1)}, 33.160 + {"local_error", (-2)}, 33.161 + {"encoding_error", (-3)}, 33.162 + {"decoding_error", (-4)}, 33.163 + {"timeout", (-5)}, 33.164 + {"auth_unknown", (-6)}, 33.165 + {"filter_error", (-7)}, 33.166 + {"user_cancelled", (-8)}, 33.167 + {"param_error", (-9)}, 33.168 + {"no_memory", (-10)}, 33.169 + {"connect_error", (-11)}, 33.170 + {"not_supported", (-12)}, 33.171 + {"control_not_found", (-13)}, 33.172 + {"no_results_returned", (-14)}, 33.173 + {"more_results_to_return", (-15)}, 33.174 + {"client_loop", (-16)}, 33.175 + {"referral_limit_exceeded", (-17)}, 33.176 + {"x_connecting", (-18)}, 33.177 + {NULL, 0} 33.178 +}; 33.179 + 33.180 +void mldap_set_errorcodes(lua_State *L) { 33.181 + // stores mldap_errorcodes[] mappings in the Lua table on top of the stack 33.182 + // in both directions (string mapped to integer and vice versa) 33.183 + 33.184 + const struct mldap_errorcode *errorcode; // pointer to entry in mldap_errorcodes[] array 33.185 + 33.186 + // iterate through entries in mldap_errorcodes[] array: 33.187 + for (errorcode=mldap_errorcodes; errorcode->ident; errorcode++) { 33.188 + 33.189 + // store a mapping from the string to the integer: 33.190 + lua_pushstring(L, errorcode->ident); 33.191 + lua_pushinteger(L, errorcode->code); 33.192 + lua_settable(L, -3); 33.193 + 33.194 + // store a mapping from the integer to the string: 33.195 + lua_pushinteger(L, errorcode->code); 33.196 + lua_pushstring(L, errorcode->ident); 33.197 + lua_settable(L, -3); 33.198 + 33.199 + } 33.200 + 33.201 +} 33.202 +
34.1 --- a/model/member.lua Thu Jul 17 23:38:35 2014 +0200 34.2 +++ b/model/member.lua Fri Jul 18 21:42:59 2014 +0200 34.3 @@ -273,6 +273,17 @@ 34.4 self.get_db_conn().query("LOCK TABLE " .. self:get_qualified_table() .. " IN ROW SHARE MODE") 34.5 end 34.6 34.7 + 34.8 +function Member:get_all_by_authority(authority) 34.9 + 34.10 + local members = Member:new_selector() 34.11 + :add_where{ "authority = ?", authority } 34.12 + :add_field("authority_data->'uid' as authority_data_uid") 34.13 + :exec() 34.14 + 34.15 + return members 34.16 +end 34.17 + 34.18 function Member.object:set_password(password) 34.19 trace.disable() 34.20 34.21 @@ -372,17 +383,144 @@ 34.22 end 34.23 34.24 function Member:by_login_and_password(login, password) 34.25 - local selector = self:new_selector() 34.26 - selector:add_field({ "now() > COALESCE(last_delegation_check, activated) + ?::interval", config.check_delegations_interval_hard }, "needs_delegation_check_hard") 34.27 - selector:add_where{'"login" = ?', login } 34.28 - selector:add_where('NOT "locked"') 34.29 - selector:optional_object_mode() 34.30 - local member = selector:exec() 34.31 - if member and member:check_password(password) then 34.32 - return member 34.33 - else 34.34 - return nil 34.35 + 34.36 + local function prepare_login_selector() 34.37 + local selector = self:new_selector() 34.38 + selector:add_field({ "now() > COALESCE(last_delegation_check, activated) + ?::interval", config.check_delegations_interval_hard }, "needs_delegation_check_hard") 34.39 + selector:add_where('NOT "locked"') 34.40 + selector:optional_object_mode() 34.41 + return selector 34.42 + end 34.43 + 34.44 + local function do_local_login() 34.45 + local selector = prepare_login_selector() 34.46 + selector:add_where{'"login" = ?', login } 34.47 + local member = selector:exec() 34.48 + if member and member:check_password(password) then 34.49 + return member 34.50 + else 34.51 + return nil 34.52 + end 34.53 end 34.54 + 34.55 + if config.ldap.member then 34.56 + 34.57 + -- Let's check the users credentials against the LDAP 34.58 + local ldap_entry, ldap_err = ldap.check_credentials(login, password) 34.59 + 34.60 + -- Is the user already registered as member? 34.61 + local uid 34.62 + local selector = prepare_login_selector() 34.63 + 34.64 + -- Get login name from LDAP entry 34.65 + if ldap_entry then 34.66 + uid = config.ldap.member.uid_map(ldap_entry) 34.67 + selector:add_where{'"authority" = ? AND "authority_data"->\'uid\' = ?', "ldap", uid } 34.68 + 34.69 + -- or build it from the login 34.70 + else 34.71 + login = config.ldap.member.login_normalizer(login) 34.72 + selector:add_where{'"authority" = ? AND "authority_data"->\'login\' = ?', "ldap", login } 34.73 + end 34.74 + 34.75 + local member = selector:exec() 34.76 + -- The member is already registered 34.77 + if member then 34.78 + 34.79 + -- The credentials entered by the user are invalid 34.80 + if ldap_err == "invalid_credentials" then 34.81 + 34.82 + -- Check if the user tried a cached password (which is invalid now) 34.83 + if config.ldap.member.cache_passwords and member:check_password(password) then 34.84 + member.password = nil 34.85 + member:save() 34.86 + end 34.87 + 34.88 + -- Try a regular login 34.89 + return do_local_login() 34.90 + 34.91 + end 34.92 + 34.93 + -- The credentials were accepted by the LDAP server and no error occured 34.94 + if ldap_entry and not ldap_err then 34.95 + 34.96 + -- Cache the password (if feature enabled) 34.97 + if config.ldap.member.cache_passwords and not member:check_password(password) then 34.98 + member:set_password(password) 34.99 + end 34.100 + 34.101 + -- update the member attributes and privileges from LDAP 34.102 + local ldap_conn, ldap_err, err, err2 = ldap.update_member_attr(member, nil, uid) 34.103 + if not err then 34.104 + local err = member:try_save() 34.105 + if err then 34.106 + return nil, "member_save_error", err 34.107 + end 34.108 + local succes, err, err2 = ldap.update_member_privileges(member, ldap_entry) 34.109 + if err then 34.110 + return nil, "update_member_privileges_error", err, err2 34.111 + end 34.112 + return member 34.113 + end 34.114 + 34.115 + end 34.116 + 34.117 + -- Some kind of LDAP error happened, if cached password are enabled, 34.118 + -- check user credentials against the cache 34.119 + if config.ldap.member.cache_passwords and member:check_password(password) then 34.120 + 34.121 + -- return the successfully logged in member 34.122 + return member 34.123 + 34.124 + end 34.125 + 34.126 + -- The member is not registered 34.127 + elseif config.ldap.member.registration and ldap_entry and not ldap_err then 34.128 + -- Automatic registration ("auto") 34.129 + if config.ldap.member.registration == "auto" then 34.130 + member = Member:new() 34.131 + member.authority = "ldap" 34.132 + local ldap_login 34.133 + if config.ldap.member.cache_passwords then 34.134 + if config.ldap.member.login_normalizer then 34.135 + ldap_login = config.ldap.member.login_normalizer(login) 34.136 + else 34.137 + ldap_login = login 34.138 + end 34.139 + end 34.140 + -- TODO change this when SQL layers supports hstore 34.141 + member.authority_data = encode.pg_hstore{ 34.142 + uid = uid, 34.143 + login = ldap_login 34.144 + } 34.145 + member.activated = "now" 34.146 + member.last_activity = "now" 34.147 + if config.ldap.member.cache_passwords then 34.148 + member:set_password(password) 34.149 + end 34.150 + local ldap_conn, ldap_err, err, err2 = ldap.update_member_attr(member, nil, uid) 34.151 + if not err then 34.152 + local err = member:try_save() 34.153 + if err then 34.154 + return nil, "member_save_error", err 34.155 + end 34.156 + local success, err, err2 = ldap.update_member_privileges(member, ldap_entry) 34.157 + if err then 34.158 + return nil, "update_member_privileges_error", err, err2 34.159 + end 34.160 + return member 34.161 + end 34.162 + 34.163 + -- No automatic registration 34.164 + else 34.165 + return nil, "ldap_credentials_valid_but_no_member", uid 34.166 + end 34.167 + end 34.168 + 34.169 + end 34.170 + 34.171 + return do_local_login() 34.172 + 34.173 end 34.174 34.175 function Member:by_login(login)
35.1 --- a/model/privilege.lua Thu Jul 17 23:38:35 2014 +0200 35.2 +++ b/model/privilege.lua Fri Jul 18 21:42:59 2014 +0200 35.3 @@ -23,4 +23,10 @@ 35.4 :add_where{ "unit_id = ? AND member_id = ?", unit_id, member_id } 35.5 :optional_object_mode() 35.6 :exec() 35.7 +end 35.8 + 35.9 +function Privilege:by_member_id(member_id) 35.10 + return self:new_selector() 35.11 + :add_where{ "member_id = ?", member_id } 35.12 + :exec() 35.13 end 35.14 \ No newline at end of file
36.1 --- a/model/session.lua Thu Jul 17 23:38:35 2014 +0200 36.2 +++ b/model/session.lua Fri Jul 18 21:42:59 2014 +0200 36.3 @@ -28,6 +28,7 @@ 36.4 function Session:by_ident(ident) 36.5 local selector = self:new_selector() 36.6 selector:add_where{ 'ident = ?', ident } 36.7 + selector:add_field{ '"authority_data" -> \'uid\' as authority_data_uid' } 36.8 selector:optional_object_mode() 36.9 return selector:exec() 36.10 end