Software / code / prosody
Comparison
plugins/mod_register_ibr.lua @ 8484:f591855f060d
mod_register: Split into mod_register_ibr and mod_user_account_management (#723)
- mod_register_ibr handles in-band registration
- mod_user_account_management handles password change and user deletion
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Sat, 07 Oct 2017 22:00:50 +0200 |
| parent | 8464:plugins/mod_register.lua@1a0b76b07b7a |
| child | 8485:0e02c6de5c02 |
comparison
equal
deleted
inserted
replaced
| 8483:6d47b74926dd | 8484:f591855f060d |
|---|---|
| 1 -- Prosody IM | |
| 2 -- Copyright (C) 2008-2010 Matthew Wild | |
| 3 -- Copyright (C) 2008-2010 Waqas Hussain | |
| 4 -- | |
| 5 -- This project is MIT/X11 licensed. Please see the | |
| 6 -- COPYING file in the source package for more information. | |
| 7 -- | |
| 8 | |
| 9 | |
| 10 local st = require "util.stanza"; | |
| 11 local dataform_new = require "util.dataforms".new; | |
| 12 local usermanager_user_exists = require "core.usermanager".user_exists; | |
| 13 local usermanager_create_user = require "core.usermanager".create_user; | |
| 14 local usermanager_delete_user = require "core.usermanager".delete_user; | |
| 15 local nodeprep = require "util.encodings".stringprep.nodeprep; | |
| 16 local create_throttle = require "util.throttle".create; | |
| 17 local new_cache = require "util.cache".new; | |
| 18 local ip_util = require "util.ip"; | |
| 19 local new_ip = ip_util.new_ip; | |
| 20 local match_ip = ip_util.match; | |
| 21 local parse_cidr = ip_util.parse_cidr; | |
| 22 | |
| 23 local additional_fields = module:get_option("additional_registration_fields", {}); | |
| 24 local require_encryption = module:get_option_boolean("c2s_require_encryption", | |
| 25 module:get_option_boolean("require_encryption", false)); | |
| 26 | |
| 27 local account_details = module:open_store("account_details"); | |
| 28 | |
| 29 local field_map = { | |
| 30 username = { name = "username", type = "text-single", label = "Username", required = true }; | |
| 31 password = { name = "password", type = "text-private", label = "Password", required = true }; | |
| 32 nick = { name = "nick", type = "text-single", label = "Nickname" }; | |
| 33 name = { name = "name", type = "text-single", label = "Full Name" }; | |
| 34 first = { name = "first", type = "text-single", label = "Given Name" }; | |
| 35 last = { name = "last", type = "text-single", label = "Family Name" }; | |
| 36 email = { name = "email", type = "text-single", label = "Email" }; | |
| 37 address = { name = "address", type = "text-single", label = "Street" }; | |
| 38 city = { name = "city", type = "text-single", label = "City" }; | |
| 39 state = { name = "state", type = "text-single", label = "State" }; | |
| 40 zip = { name = "zip", type = "text-single", label = "Postal code" }; | |
| 41 phone = { name = "phone", type = "text-single", label = "Telephone number" }; | |
| 42 url = { name = "url", type = "text-single", label = "Webpage" }; | |
| 43 date = { name = "date", type = "text-single", label = "Birth date" }; | |
| 44 }; | |
| 45 | |
| 46 local title = module:get_option_string("registration_title", | |
| 47 "Creating a new account"); | |
| 48 local instructions = module:get_option_string("registration_instructions", | |
| 49 "Choose a username and password for use with this service."); | |
| 50 | |
| 51 local registration_form = dataform_new{ | |
| 52 title = title; | |
| 53 instructions = instructions; | |
| 54 | |
| 55 field_map.username; | |
| 56 field_map.password; | |
| 57 }; | |
| 58 | |
| 59 local registration_query = st.stanza("query", {xmlns = "jabber:iq:register"}) | |
| 60 :tag("instructions"):text(instructions):up() | |
| 61 :tag("username"):up() | |
| 62 :tag("password"):up(); | |
| 63 | |
| 64 for _, field in ipairs(additional_fields) do | |
| 65 if type(field) == "table" then | |
| 66 registration_form[#registration_form + 1] = field; | |
| 67 elseif field_map[field] or field_map[field:sub(1, -2)] then | |
| 68 if field:match("%+$") then | |
| 69 field = field:sub(1, -2); | |
| 70 field_map[field].required = true; | |
| 71 end | |
| 72 | |
| 73 registration_form[#registration_form + 1] = field_map[field]; | |
| 74 registration_query:tag(field):up(); | |
| 75 else | |
| 76 module:log("error", "Unknown field %q", field); | |
| 77 end | |
| 78 end | |
| 79 registration_query:add_child(registration_form:form()); | |
| 80 | |
| 81 local register_stream_feature = st.stanza("register", {xmlns="http://jabber.org/features/iq-register"}):up(); | |
| 82 module:hook("stream-features", function(event) | |
| 83 local session, features = event.origin, event.features; | |
| 84 | |
| 85 -- Advertise registration to unauthorized clients only. | |
| 86 if session.type ~= "c2s_unauthed" or (require_encryption and not session.secure) then | |
| 87 return | |
| 88 end | |
| 89 | |
| 90 features:add_child(register_stream_feature); | |
| 91 end); | |
| 92 | |
| 93 local function parse_response(query) | |
| 94 local form = query:get_child("x", "jabber:x:data"); | |
| 95 if form then | |
| 96 return registration_form:data(form); | |
| 97 else | |
| 98 local data = {}; | |
| 99 local errors = {}; | |
| 100 for _, field in ipairs(registration_form) do | |
| 101 local name, required = field.name, field.required; | |
| 102 if field_map[name] then | |
| 103 data[name] = query:get_child_text(name); | |
| 104 if (not data[name] or #data[name] == 0) and required then | |
| 105 errors[name] = "Required value missing"; | |
| 106 end | |
| 107 end | |
| 108 end | |
| 109 if next(errors) then | |
| 110 return data, errors; | |
| 111 end | |
| 112 return data; | |
| 113 end | |
| 114 end | |
| 115 | |
| 116 local min_seconds_between_registrations = module:get_option_number("min_seconds_between_registrations"); | |
| 117 local whitelist_only = module:get_option_boolean("whitelist_registration_only"); | |
| 118 local whitelisted_ips = module:get_option_set("registration_whitelist", { "127.0.0.1", "::1" })._items; | |
| 119 local blacklisted_ips = module:get_option_set("registration_blacklist", {})._items; | |
| 120 | |
| 121 local throttle_max = module:get_option_number("registration_throttle_max", min_seconds_between_registrations and 1); | |
| 122 local throttle_period = module:get_option_number("registration_throttle_period", min_seconds_between_registrations); | |
| 123 local throttle_cache_size = module:get_option_number("registration_throttle_cache_size", 100); | |
| 124 local blacklist_overflow = module:get_option_boolean("blacklist_on_registration_throttle_overload", false); | |
| 125 | |
| 126 local throttle_cache = new_cache(throttle_cache_size, blacklist_overflow and function (ip, throttle) | |
| 127 if not throttle:peek() then | |
| 128 module:log("info", "Adding ip %s to registration blacklist", ip); | |
| 129 blacklisted_ips[ip] = true; | |
| 130 end | |
| 131 end or nil); | |
| 132 | |
| 133 local function check_throttle(ip) | |
| 134 if not throttle_max then return true end | |
| 135 local throttle = throttle_cache:get(ip); | |
| 136 if not throttle then | |
| 137 throttle = create_throttle(throttle_max, throttle_period); | |
| 138 end | |
| 139 throttle_cache:set(ip, throttle); | |
| 140 return throttle:poll(1); | |
| 141 end | |
| 142 | |
| 143 local function ip_in_set(set, ip) | |
| 144 if set[ip] then | |
| 145 return true; | |
| 146 end | |
| 147 ip = new_ip(ip); | |
| 148 for in_set in pairs(set) do | |
| 149 if match_ip(ip, parse_cidr(in_set)) then | |
| 150 return true; | |
| 151 end | |
| 152 end | |
| 153 return false; | |
| 154 end | |
| 155 | |
| 156 -- In-band registration | |
| 157 module:hook("stanza/iq/jabber:iq:register:query", function(event) | |
| 158 local session, stanza = event.origin, event.stanza; | |
| 159 local log = session.log or module._log; | |
| 160 | |
| 161 if session.type ~= "c2s_unauthed" then | |
| 162 log("debug", "Attempted registration when disabled or already authenticated"); | |
| 163 session.send(st.error_reply(stanza, "cancel", "service-unavailable")); | |
| 164 elseif require_encryption and not session.secure then | |
| 165 session.send(st.error_reply(stanza, "modify", "policy-violation", "Encryption is required")); | |
| 166 else | |
| 167 local query = stanza.tags[1]; | |
| 168 if stanza.attr.type == "get" then | |
| 169 local reply = st.reply(stanza); | |
| 170 reply:add_child(registration_query); | |
| 171 session.send(reply); | |
| 172 elseif stanza.attr.type == "set" then | |
| 173 if query.tags[1] and query.tags[1].name == "remove" then | |
| 174 session.send(st.error_reply(stanza, "auth", "registration-required")); | |
| 175 else | |
| 176 local data, errors = parse_response(query); | |
| 177 if errors then | |
| 178 log("debug", "Error parsing registration form:"); | |
| 179 for field, err in pairs(errors) do | |
| 180 log("debug", "Field %q: %s", field, err); | |
| 181 end | |
| 182 session.send(st.error_reply(stanza, "modify", "not-acceptable")); | |
| 183 else | |
| 184 -- Check that the user is not blacklisted or registering too often | |
| 185 if not session.ip then | |
| 186 log("debug", "User's IP not known; can't apply blacklist/whitelist"); | |
| 187 elseif ip_in_set(blacklisted_ips, session.ip) or (whitelist_only and not ip_in_set(whitelisted_ips, session.ip)) then | |
| 188 session.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not allowed to register an account.")); | |
| 189 return true; | |
| 190 elseif throttle_max and not ip_in_set(whitelisted_ips, session.ip) then | |
| 191 if not check_throttle(session.ip) then | |
| 192 log("debug", "Registrations over limit for ip %s", session.ip or "?"); | |
| 193 session.send(st.error_reply(stanza, "wait", "not-acceptable")); | |
| 194 return true; | |
| 195 end | |
| 196 end | |
| 197 local username, password = nodeprep(data.username), data.password; | |
| 198 data.username, data.password = nil, nil; | |
| 199 local host = module.host; | |
| 200 if not username or username == "" then | |
| 201 log("debug", "The requested username is invalid."); | |
| 202 session.send(st.error_reply(stanza, "modify", "not-acceptable", "The requested username is invalid.")); | |
| 203 return true; | |
| 204 end | |
| 205 local user = { username = username , host = host, additional = data, ip = session.ip, session = session, allowed = true } | |
| 206 module:fire_event("user-registering", user); | |
| 207 if not user.allowed then | |
| 208 log("debug", "Registration disallowed by module"); | |
| 209 session.send(st.error_reply(stanza, "modify", "not-acceptable", "The requested username is forbidden.")); | |
| 210 elseif usermanager_user_exists(username, host) then | |
| 211 log("debug", "Attempt to register with existing username"); | |
| 212 session.send(st.error_reply(stanza, "cancel", "conflict", "The requested username already exists.")); | |
| 213 else | |
| 214 -- TODO unable to write file, file may be locked, etc, what's the correct error? | |
| 215 local error_reply = st.error_reply(stanza, "wait", "internal-server-error", "Failed to write data to disk."); | |
| 216 if usermanager_create_user(username, password, host) then | |
| 217 data.registered = os.time(); | |
| 218 if not account_details:set(username, data) then | |
| 219 log("debug", "Could not store extra details"); | |
| 220 usermanager_delete_user(username, host); | |
| 221 session.send(error_reply); | |
| 222 return true; | |
| 223 end | |
| 224 session.send(st.reply(stanza)); -- user created! | |
| 225 log("info", "User account created: %s@%s", username, host); | |
| 226 module:fire_event("user-registered", { | |
| 227 username = username, host = host, source = "mod_register", | |
| 228 session = session }); | |
| 229 else | |
| 230 log("debug", "Could not create user"); | |
| 231 session.send(error_reply); | |
| 232 end | |
| 233 end | |
| 234 end | |
| 235 end | |
| 236 end | |
| 237 end | |
| 238 return true; | |
| 239 end); |