bsw/jbe@0: Member = mondelefant.new_class() bsw/jbe@0: Member.table = 'member' bsw/jbe@0: jbe@1232: local function secret_token() jbe@1232: local parts = {} jbe@1232: for i = 1, 5 do jbe@1232: parts[#parts+1] = multirand.string(5, "23456789bcdfghjkmnpqrstvwxyz") jbe@1232: end jbe@1232: return (table.concat(parts, "-")) jbe@1232: end jbe@1232: bsw/jbe@0: Member:add_reference{ bsw@9: mode = "1m", bsw@9: to = "MemberHistory", bsw@9: this_key = 'id', bsw@9: that_key = 'member_id', bsw@9: ref = 'history_entries', bsw@9: back_ref = 'member' bsw@9: } bsw@9: bsw@9: Member:add_reference{ bsw/jbe@4: mode = '1m', bsw@2: to = "MemberImage", bsw@2: this_key = 'id', bsw@2: that_key = 'member_id', bsw/jbe@4: ref = 'images', bsw@2: back_ref = 'member' bsw@2: } bsw@2: bsw@2: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Contact", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'member_id', bsw/jbe@0: ref = 'contacts', bsw/jbe@0: back_ref = 'member', bsw/jbe@0: default_order = '"other_member_id"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Contact", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'member_id', bsw/jbe@0: ref = 'foreign_contacts', bsw/jbe@0: back_ref = 'other_member', bsw/jbe@0: default_order = '"member_id"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Session", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'member_id', bsw/jbe@0: ref = 'sessions', bsw/jbe@0: back_ref = 'member', bsw/jbe@0: default_order = '"ident"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Draft", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'author_id', bsw/jbe@0: ref = 'drafts', bsw/jbe@0: back_ref = 'author', bsw/jbe@0: default_order = '"id"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Suggestion", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'author_id', bsw/jbe@0: ref = 'suggestions', bsw/jbe@0: back_ref = 'author', bsw/jbe@0: default_order = '"id"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Membership", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'member_id', bsw/jbe@0: ref = 'memberships', bsw/jbe@0: back_ref = 'member', bsw/jbe@0: default_order = '"area_id"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Interest", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'member_id', bsw/jbe@0: ref = 'interests', bsw/jbe@0: back_ref = 'member', bsw/jbe@0: default_order = '"id"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Initiator", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'member_id', bsw/jbe@0: ref = 'initiators', bsw@10: back_ref = 'member' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Supporter", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'member_id', bsw/jbe@0: ref = 'supporters', bsw@2: back_ref = 'member' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Opinion", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'member_id', bsw/jbe@0: ref = 'opinions', bsw/jbe@0: back_ref = 'member', bsw/jbe@0: default_order = '"id"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Delegation", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'truster_id', bsw/jbe@0: ref = 'outgoing_delegations', bsw/jbe@0: back_ref = 'truster', bsw@1045: -- default_order = '"id"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Delegation", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'trustee_id', bsw/jbe@0: ref = 'incoming_delegations', bsw/jbe@0: back_ref = 'trustee', bsw@1045: -- default_order = '"id"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "DirectVoter", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'member_id', bsw/jbe@0: ref = 'direct_voter', bsw/jbe@0: back_ref = 'member', bsw/jbe@0: default_order = '"issue_id"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = '1m', bsw/jbe@0: to = "Vote", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'member_id', bsw/jbe@0: ref = 'vote', bsw/jbe@0: back_ref = 'member', bsw/jbe@0: default_order = '"issue_id", "initiative_id"' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@1309: mode = '11', bsw/jbe@1309: to = "MemberProfile", bsw/jbe@1309: this_key = 'id', bsw/jbe@1309: that_key = 'member_id', bsw/jbe@1309: ref = 'profile', bsw/jbe@1309: back_ref = 'member' bsw/jbe@1309: } bsw/jbe@1309: bsw/jbe@1309: Member:add_reference{ bsw/jbe@1309: mode = '11', bsw/jbe@1309: to = "MemberSettings", bsw/jbe@1309: this_key = 'id', bsw/jbe@1309: that_key = 'member_id', bsw/jbe@1309: ref = 'settings', bsw/jbe@1309: back_ref = 'member' bsw/jbe@1309: } bsw/jbe@1309: bsw/jbe@1309: Member:add_reference{ bsw/jbe@0: mode = 'mm', bsw/jbe@0: to = "Member", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'id', bsw/jbe@0: connected_by_table = 'contact', bsw/jbe@0: connected_by_this_key = 'member_id', bsw/jbe@0: connected_by_that_key = 'other_member_id', bsw/jbe@0: ref = 'saved_members', bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = 'mm', bsw/jbe@0: to = "Member", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'id', bsw/jbe@0: connected_by_table = 'contact', bsw/jbe@0: connected_by_this_key = 'other_member_id', bsw/jbe@0: connected_by_that_key = 'member_id', bsw/jbe@0: ref = 'saved_by_members', bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = 'mm', bsw@281: to = "Unit", bsw@281: this_key = 'id', bsw@281: that_key = 'id', bsw@281: connected_by_table = 'privilege', bsw@281: connected_by_this_key = 'member_id', bsw@281: connected_by_that_key = 'unit_id', bsw@281: ref = 'units' bsw@281: } bsw@281: bsw@281: Member:add_reference{ bsw@281: mode = 'mm', bsw/jbe@0: to = "Area", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'id', bsw/jbe@0: connected_by_table = 'membership', bsw/jbe@0: connected_by_this_key = 'member_id', bsw/jbe@0: connected_by_that_key = 'area_id', bsw/jbe@0: ref = 'areas' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = 'mm', bsw/jbe@0: to = "Issue", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'id', bsw/jbe@0: connected_by_table = 'interest', bsw/jbe@0: connected_by_this_key = 'member_id', bsw/jbe@0: connected_by_that_key = 'issue_id', bsw/jbe@0: ref = 'issues' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = 'mm', bsw/jbe@0: to = "Initiative", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'id', bsw/jbe@0: connected_by_table = 'initiator', bsw/jbe@0: connected_by_this_key = 'member_id', bsw/jbe@0: connected_by_that_key = 'initiative_id', bsw/jbe@0: ref = 'initiated_initiatives' bsw/jbe@0: } bsw/jbe@0: bsw/jbe@0: Member:add_reference{ bsw/jbe@0: mode = 'mm', bsw/jbe@0: to = "Initiative", bsw/jbe@0: this_key = 'id', bsw/jbe@0: that_key = 'id', bsw/jbe@0: connected_by_table = 'supporter', bsw/jbe@0: connected_by_this_key = 'member_id', bsw/jbe@0: connected_by_that_key = 'initiative_id', bsw/jbe@0: ref = 'supported_initiatives' bsw/jbe@0: } bsw/jbe@0: bsw@193: function Member:build_selector(args) bsw@193: local selector = self:new_selector() bsw@193: if args.active ~= nil then bsw@193: selector:add_where{ "member.active = ?", args.active } bsw@193: end bsw@581: if args.locked ~= nil then bsw@581: selector:add_where{ "member.locked = ?", args.locked } bsw@581: end bsw@199: if args.is_contact_of_member_id then bsw@199: selector:join("contact", "__model_member__contact", "member.id = __model_member__contact.other_member_id") bsw@199: selector:add_where{ "__model_member__contact.member_id = ?", args.is_contact_of_member_id } bsw@199: end bsw@297: if args.voting_right_for_unit_id then bsw@299: selector:join("privilege", "__model_member__privilege", { "member.id = __model_member__privilege.member_id AND __model_member__privilege.voting_right AND __model_member__privilege.unit_id = ?", args.voting_right_for_unit_id }) bsw@297: end bsw@581: if args.admin_search then bsw@581: local search_string = "%" .. args.admin_search .. "%" bsw@581: selector:add_where{ "member.identification ILIKE ? OR member.name ILIKE ?", search_string, search_string } bsw@581: end bsw@193: if args.order then bsw@193: if args.order == "id" then bsw@193: selector:add_order_by("id") bsw@581: elseif args.order == "identification" then bsw@581: selector:add_order_by("identification") bsw@193: elseif args.order == "name" then bsw@193: selector:add_order_by("name") bsw@193: else bsw@193: error("invalid order") bsw@193: end bsw@193: end bsw@193: return selector bsw@193: end bsw@193: bsw@929: function Member:lockForReference() bsw@929: self.get_db_conn().query("LOCK TABLE " .. self:get_qualified_table() .. " IN ROW SHARE MODE") bsw@929: end bsw@929: bsw@1071: bsw@1071: function Member:get_all_by_authority(authority) bsw@1071: bsw@1071: local members = Member:new_selector() bsw@1071: :add_where{ "authority = ?", authority } bsw@1074: :add_field("authority_uid") bsw@1071: :exec() bsw@1071: bsw@1071: return members bsw@1071: end bsw@1071: bsw/jbe@0: function Member.object:set_password(password) bsw@865: trace.disable() bsw@905: bsw@905: local hash_prefix bsw@905: local salt_length bsw@905: bsw@905: local function rounds() bsw@905: return multirand.integer( bsw@905: config.password_hash_min_rounds, bsw@905: config.password_hash_max_rounds bsw@905: ) bsw@905: end bsw@905: bsw@905: if config.password_hash_algorithm == "crypt_md5" then bsw@905: hash_prefix = "$1$" bsw@905: salt_length = 8 bsw@905: bsw@905: elseif config.password_hash_algorithm == "crypt_sha256" then bsw@905: hash_prefix = "$5$rounds=" .. rounds() .. "$" bsw@905: salt_length = 16 bsw@905: bsw@905: elseif config.password_hash_algorithm == "crypt_sha512" then bsw@905: hash_prefix = "$6$rounds=" .. rounds() .. "$" bsw@905: salt_length = 16 bsw@905: bsw@905: else bsw@905: error("Unknown hash algorithm selected in configuration") bsw@905: bsw@905: end bsw@906: bsw@906: hash_prefix = hash_prefix .. multirand.string( bsw@906: salt_length, bsw@906: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./" bsw@906: ) bsw@905: bsw@906: local hash = extos.crypt(password, hash_prefix) bsw@905: bsw@905: if not hash or hash:sub(1, #hash_prefix) ~= hash_prefix then bsw@905: error("Password hashing algorithm failed") bsw@905: end bsw@905: bsw/jbe@0: self.password = hash bsw@1231: self.password_reset_secret = nil bsw@1231: self.password_reset_secret_expiry = nil bsw/jbe@0: end bsw/jbe@0: bsw/jbe@0: function Member.object:check_password(password) bsw/jbe@0: if type(password) == "string" and type(self.password) == "string" then bsw@728: return extos.crypt(password, self.password) == self.password bsw/jbe@0: else bsw/jbe@0: return false bsw/jbe@0: end bsw/jbe@0: end bsw/jbe@0: bsw@905: function Member.object_get:password_hash_needs_update() bsw@905: bsw@905: if self.password == nil then bsw@905: return nil bsw@905: end bsw@905: bsw@905: local function check_rounds(rounds) bsw@905: if rounds then bsw@905: rounds = tonumber(rounds) bsw@905: if bsw@905: rounds >= config.password_hash_min_rounds and bsw@905: rounds <= config.password_hash_max_rounds bsw@905: then bsw@905: return false bsw@905: end bsw@905: end bsw@905: return true bsw@905: end bsw@905: bsw@905: if config.password_hash_algorithm == "crypt_md5" then bsw@905: bsw@905: return self.password:sub(1,3) ~= "$1$" bsw@905: bsw@905: elseif config.password_hash_algorithm == "crypt_sha256" then bsw@905: bsw@905: return check_rounds(self.password:match("^%$5%$rounds=([1-9][0-9]*)%$")) bsw@905: bsw@905: elseif config.password_hash_algorithm == "crypt_sha512" then bsw@905: bsw@905: return check_rounds(self.password:match("^%$6%$rounds=([1-9][0-9]*)%$")) bsw@905: bsw@905: else bsw@905: error("Unknown hash algorithm selected in configuration") bsw@905: bsw@905: end bsw@905: bsw@905: end bsw@905: bsw/jbe@0: function Member.object_get:published_contacts() bsw/jbe@0: return Member:new_selector() bsw/jbe@0: :join('"contact"', nil, '"contact"."other_member_id" = "member"."id"') bsw/jbe@0: :add_where{ '"contact"."member_id" = ?', self.id } bsw/jbe@0: :add_where("public") bsw/jbe@0: :exec() bsw/jbe@0: end bsw/jbe@0: bsw/jbe@0: function Member:by_login_and_password(login, password) bsw@1071: bsw@1071: local function prepare_login_selector() bsw@1071: local selector = self:new_selector() bsw@1071: selector:add_field({ "now() > COALESCE(last_delegation_check, activated) + ?::interval", config.check_delegations_interval_hard }, "needs_delegation_check_hard") bsw@1071: selector:add_where('NOT "locked"') bsw@1071: selector:optional_object_mode() bsw@1071: return selector bsw@1071: end bsw@1071: bsw@1071: local function do_local_login() bsw@1071: local selector = prepare_login_selector() bsw@1071: selector:add_where{'"login" = ?', login } bsw@1071: local member = selector:exec() bsw@1071: if member and member:check_password(password) then bsw@1071: return member bsw@1071: else bsw@1071: return nil bsw@1071: end bsw/jbe@0: end bsw@1071: bsw@1071: if config.ldap.member then bsw@1071: bsw@1071: -- Let's check the users credentials against the LDAP bsw@1071: local ldap_entry, ldap_err = ldap.check_credentials(login, password) bsw@1071: bsw@1071: -- Is the user already registered as member? bsw@1071: local uid bsw@1071: local selector = prepare_login_selector() bsw@1071: bsw@1071: -- Get login name from LDAP entry bsw@1071: if ldap_entry then bsw@1071: uid = config.ldap.member.uid_map(ldap_entry) bsw@1074: selector:add_where{'"authority" = ? AND "authority_uid" = ?', "ldap", uid } bsw@1071: bsw@1071: -- or build it from the login bsw@1071: else bsw@1071: login = config.ldap.member.login_normalizer(login) bsw@1074: selector:add_where{'"authority" = ? AND "authority_uid" = ?', "ldap", login } bsw@1071: end bsw@1071: bsw@1071: local member = selector:exec() bsw@1071: -- The member is already registered bsw@1071: if member then bsw@1071: bsw@1071: -- The credentials entered by the user are invalid bsw@1071: if ldap_err == "invalid_credentials" then bsw@1071: bsw@1071: -- Check if the user tried a cached password (which is invalid now) bsw@1071: if config.ldap.member.cache_passwords and member:check_password(password) then bsw@1071: member.password = nil bsw@1071: member:save() bsw@1071: end bsw@1071: bsw@1071: -- Try a regular login bsw@1071: return do_local_login() bsw@1071: bsw@1071: end bsw@1071: bsw@1071: -- The credentials were accepted by the LDAP server and no error occured bsw@1071: if ldap_entry and not ldap_err then bsw@1071: bsw@1071: -- Cache the password (if feature enabled) bsw@1071: if config.ldap.member.cache_passwords and not member:check_password(password) then bsw@1071: member:set_password(password) bsw@1071: end bsw@1071: bsw@1071: -- update the member attributes and privileges from LDAP bsw@1071: local ldap_conn, ldap_err, err, err2 = ldap.update_member_attr(member, nil, uid) bsw@1071: if not err then bsw@1071: local err = member:try_save() bsw@1071: if err then bsw@1071: return nil, "member_save_error", err bsw@1071: end bsw@1071: local succes, err, err2 = ldap.update_member_privileges(member, ldap_entry) bsw@1071: if err then bsw@1071: return nil, "update_member_privileges_error", err, err2 bsw@1071: end bsw@1071: return member bsw@1071: end bsw@1071: bsw@1071: end bsw@1071: bsw@1071: -- Some kind of LDAP error happened, if cached password are enabled, bsw@1071: -- check user credentials against the cache bsw@1071: if config.ldap.member.cache_passwords and member:check_password(password) then bsw@1071: bsw@1071: -- return the successfully logged in member bsw@1071: return member bsw@1071: bsw@1071: end bsw@1071: bsw@1071: -- The member is not registered bsw@1071: elseif config.ldap.member.registration and ldap_entry and not ldap_err then bsw@1071: -- Automatic registration ("auto") bsw@1071: if config.ldap.member.registration == "auto" then bsw@1071: member = Member:new() bsw@1071: member.authority = "ldap" bsw@1071: local ldap_login bsw@1071: if config.ldap.member.cache_passwords then bsw@1071: if config.ldap.member.login_normalizer then bsw@1071: ldap_login = config.ldap.member.login_normalizer(login) bsw@1071: else bsw@1071: ldap_login = login bsw@1071: end bsw@1071: end bsw@1071: -- TODO change this when SQL layers supports hstore bsw@1074: member.authority_uid = uid bsw@1074: member.authority_login = ldap_login bsw@1071: member.activated = "now" bsw@1071: member.last_activity = "now" bsw@1071: if config.ldap.member.cache_passwords then bsw@1071: member:set_password(password) bsw@1071: end bsw@1071: local ldap_conn, ldap_err, err, err2 = ldap.update_member_attr(member, nil, uid) bsw@1071: if not err then bsw@1071: local err = member:try_save() bsw@1071: if err then bsw@1071: return nil, "member_save_error", err bsw@1071: end bsw@1071: local success, err, err2 = ldap.update_member_privileges(member, ldap_entry) bsw@1071: if err then bsw@1071: return nil, "update_member_privileges_error", err, err2 bsw@1071: end bsw@1071: return member bsw@1071: end bsw@1071: bsw@1071: -- No automatic registration bsw@1071: else bsw@1071: return nil, "ldap_credentials_valid_but_no_member", uid bsw@1071: end bsw@1071: end bsw@1071: bsw@1071: end bsw@1071: bsw@1071: return do_local_login() bsw@1071: bsw/jbe@0: end bsw/jbe@0: bsw/jbe@1309: function Member:by_ids(ids) bsw/jbe@1309: local selector = self:new_selector() bsw/jbe@1309: selector:add_where{'"id" IN ($)', { ids } } bsw/jbe@1309: return selector:exec() bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@5: function Member:by_login(login) bsw/jbe@5: local selector = self:new_selector() bsw/jbe@5: selector:add_where{'"login" = ?', login } bsw/jbe@5: selector:optional_object_mode() bsw/jbe@5: return selector:exec() bsw/jbe@5: end bsw/jbe@5: bsw/jbe@5: function Member:by_name(name) bsw/jbe@5: local selector = self:new_selector() bsw/jbe@5: selector:add_where{'"name" = ?', name } bsw/jbe@5: selector:optional_object_mode() bsw/jbe@5: return selector:exec() bsw/jbe@5: end bsw/jbe@5: bsw@2: function Member:get_search_selector(search_string) bsw/jbe@0: return self:new_selector() bsw@2: :add_field( {'"highlight"("member"."name", ?)', search_string }, "name_highlighted") bsw@2: :add_where{ '"member"."text_search_data" @@ "text_search_query"(?)', search_string } bsw@362: :add_where("activated NOTNULL AND active") bsw/jbe@0: end bsw@2: bsw@1231: function Member.object:send_password_reset_mail() bsw@1231: trace.disable() bsw@1231: if not self.notify_email then bsw@1231: return false bsw@1231: end jbe@1232: self.password_reset_secret = secret_token() bsw@1231: local expiry = db:query("SELECT now() + '1 days'::interval as expiry", "object").expiry bsw@1231: self.password_reset_secret_expiry = expiry bsw@1231: self:save() bsw@1231: local content = slot.use_temporary(function() bsw@1231: slot.put(_"Hello " .. self.name .. ",\n\n") bsw@1231: slot.put(_"to reset your password please click on the following link:\n\n") bsw@1231: slot.put(request.get_absolute_baseurl() .. "index/reset_password.html?secret=" .. self.password_reset_secret .. "\n\n") bsw@1231: slot.put(_"If this link is not working, please open following url in your web browser:\n\n") bsw@1231: slot.put(request.get_absolute_baseurl() .. "index/reset_password.html\n\n") bsw@1231: slot.put(_"On that page please enter the reset code:\n\n") bsw@1231: slot.put(self.password_reset_secret .. "\n\n") bsw@1231: end) bsw@1231: local success = net.send_mail{ bsw@1231: envelope_from = config.mail_envelope_from, bsw@1231: from = config.mail_from, bsw@1231: reply_to = config.mail_reply_to, bsw@1231: to = self.notify_email, bsw@1231: subject = config.mail_subject_prefix .. _"Password reset request", bsw@1231: content_type = "text/plain; charset=UTF-8", bsw@1231: content = content bsw@1231: } bsw@1231: return success bsw@1231: end bsw@1231: bsw@388: function Member.object:send_invitation(template_file, subject) bsw@286: trace.disable() jbe@1232: self.invite_code = secret_token() bsw@388: self:save() bsw@388: bsw@388: local subject = subject bsw@388: local content bsw/jbe@1309: local baseurl = request.get_absolute_baseurl() .. "index/register.html" bsw/jbe@1309: local url = baseurl .. "?skip=1&code=" .. self.invite_code bsw@388: if template_file then bsw@388: local fh = io.open(template_file, "r") bsw@388: content = fh:read("*a") bsw/jbe@1309: content = content:gsub("#{url}", url):gsub("#{baseurl}", baseurl):gsub("#{code}", self.invite_code) bsw/jbe@1309: elseif config.invitation_mail then bsw/jbe@1309: subject = config.invitation_mail.subject bsw/jbe@1309: content = config.invitation_mail.content:gsub("#{url}", url):gsub("#{baseurl}", baseurl):gsub("#{code}", self.invite_code) bsw@388: else bsw@388: subject = config.mail_subject_prefix .. _"Invitation to LiquidFeedback" bsw@388: content = slot.use_temporary(function() bsw@388: slot.put(_"Hello\n\n") bsw@388: slot.put(_"You are invited to LiquidFeedback. To register please click the following link:\n\n") bsw/jbe@1309: slot.put(url .. "\n\n") bsw@388: slot.put(_"If this link is not working, please open following url in your web browser:\n\n") bsw/jbe@1309: slot.put(baseurl .. "\n\n") bsw@388: slot.put(_"On that page please enter the invite key:\n\n") bsw@388: slot.put(self.invite_code .. "\n\n") bsw@388: end) bsw@388: end bsw@388: bsw@286: local success = net.send_mail{ bsw@286: envelope_from = config.mail_envelope_from, bsw@286: from = config.mail_from, bsw@286: reply_to = config.mail_reply_to, bsw@286: to = self.notify_email_unconfirmed or self.notify_email, bsw@388: subject = subject, bsw@286: content_type = "text/plain; charset=UTF-8", bsw@286: content = content bsw@286: } bsw@286: return success bsw/jbe@0: end bsw@2: bsw/jbe@6: function Member.object:set_notify_email(notify_email) bsw@224: trace.disable() bsw/jbe@6: local expiry = db:query("SELECT now() + '7 days'::interval as expiry", "object").expiry bsw/jbe@6: self.notify_email_unconfirmed = notify_email jbe@1232: self.notify_email_secret = secret_token() bsw/jbe@6: self.notify_email_secret_expiry = expiry bsw/jbe@6: local content = slot.use_temporary(function() bsw/jbe@6: slot.put(_"Hello " .. self.name .. ",\n\n") bsw/jbe@6: slot.put(_"Please confirm your email address by clicking the following link:\n\n") jbe@326: slot.put(request.get_absolute_baseurl() .. "index/confirm_notify_email.html?secret=" .. self.notify_email_secret .. "\n\n") bsw/jbe@6: slot.put(_"If this link is not working, please open following url in your web browser:\n\n") jbe@326: slot.put(request.get_absolute_baseurl() .. "index/confirm_notify_email.html\n\n") bsw/jbe@6: slot.put(_"On that page please enter the confirmation code:\n\n") bsw/jbe@6: slot.put(self.notify_email_secret .. "\n\n") bsw/jbe@6: end) bsw/jbe@6: local success = net.send_mail{ bsw/jbe@6: envelope_from = config.mail_envelope_from, bsw/jbe@6: from = config.mail_from, bsw/jbe@6: reply_to = config.mail_reply_to, bsw/jbe@6: to = self.notify_email_unconfirmed, bsw/jbe@6: subject = config.mail_subject_prefix .. _"Email confirmation request", bsw/jbe@6: content_type = "text/plain; charset=UTF-8", bsw/jbe@6: content = content bsw/jbe@6: } bsw@75: if success then bsw@75: local lock_expiry = db:query("SELECT now() + '1 hour'::interval AS lock_expiry", "object").lock_expiry bsw@75: self.notify_email_lock_expiry = lock_expiry bsw@75: end bsw@75: self:save() bsw/jbe@6: return success bsw/jbe@6: end bsw@11: bsw/jbe@19: function Member.object:get_setting(key) bsw@79: return Setting:by_pk(self.id, key) bsw/jbe@19: end bsw/jbe@19: bsw/jbe@19: function Member.object:get_setting_value(key) bsw@79: local setting = Setting:by_pk(self.id, key) bsw/jbe@19: if setting then bsw/jbe@19: return setting.value bsw/jbe@19: end bsw@11: end bsw@11: bsw@11: function Member.object:set_setting(key, value) bsw/jbe@19: local setting = self:get_setting(key) bsw/jbe@19: if not setting then bsw/jbe@19: setting = Setting:new() bsw@79: setting.member_id = self.id bsw/jbe@19: setting.key = key bsw/jbe@19: end bsw/jbe@19: setting.value = value bsw/jbe@19: setting:save() bsw@11: end bsw@11: bsw@11: function Member.object:get_setting_maps_by_key(key) bsw@11: return SettingMap:new_selector() bsw@11: :add_where{ "member_id = ?", self.id } bsw@11: :add_where{ "key = ?", key } bsw@11: :add_order_by("subkey") bsw@11: :exec() bsw@11: end bsw@11: bsw@11: function Member.object:get_setting_map_by_key_and_subkey(key, subkey) bsw@11: return SettingMap:new_selector() bsw@11: :add_where{ "member_id = ?", self.id } bsw@11: :add_where{ "key = ?", key } bsw@11: :add_where{ "subkey = ?", subkey } bsw@11: :add_order_by("subkey") bsw@11: :optional_object_mode() bsw@11: :exec() bsw@11: end bsw@11: bsw@11: function Member.object:set_setting_map(key, subkey, value) poelzi@144: setting_map = self:get_setting_map_by_key_and_subkey(key, subkey) poelzi@144: if not setting_map then poelzi@144: setting_map = SettingMap:new() poelzi@144: setting_map.member_id = self.id poelzi@144: setting_map.key = key poelzi@144: setting_map.subkey = subkey poelzi@144: end poelzi@144: setting_map.value = value poelzi@144: setting_map:save() bsw@11: end bsw@75: bsw@75: function Member.object_get:notify_email_locked() bsw@75: return( bsw@75: Member:new_selector() bsw@75: :add_where{ "id = ?", app.session.member.id } bsw@75: :add_where("notify_email_lock_expiry > now()") bsw@75: :count() == 1 bsw@75: ) poelzi@134: end poelzi@134: bsw@273: function Member.object_get:units_with_voting_right() bsw@273: return(Unit:new_selector() bsw@273: :join("privilege", nil, { "privilege.unit_id = unit.id AND privilege.member_id = ? AND privilege.voting_right", self.id }) bsw@273: :exec() bsw@273: ) bsw@273: end bsw@273: poelzi@134: function Member.object:ui_field_text(args) poelzi@134: args = args or {} bsw@813: if app.session:has_access("authors_pseudonymous") then poelzi@134: -- ugly workaround for getting html into a replaced string and to the user poelzi@134: ui.container{label = args.label, label_attr={class="ui_field_label"}, content = function() poelzi@134: slot.put(string.format('%s', poelzi@134: encode.url{ poelzi@134: module = "member", poelzi@134: view = "show", poelzi@134: id = self.id, poelzi@134: }, poelzi@134: encode.html(self.name))) poelzi@134: end poelzi@134: } poelzi@134: else poelzi@134: ui.field.text{ label = args.label, value = _"[not displayed public]" } poelzi@134: end poelzi@134: end bsw@281: bsw/jbe@1309: local function populate_units_with_initiative_right_hash(self) bsw/jbe@1309: if not self.__units_with_initiative_right_hash then bsw/jbe@1309: local privileges = Privilege:new_selector() bsw/jbe@1309: :add_where{ "member_id = ?", self.id } bsw/jbe@1309: :add_where("initiative_right") bsw/jbe@1309: :exec() bsw/jbe@1309: self.__units_with_initiative_right_hash = {} bsw/jbe@1309: for i, privilege in ipairs(privileges) do bsw/jbe@1309: self.__units_with_initiative_right_hash[privilege.unit_id] = true bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: function Member.object:has_initiative_right_for_unit_id(unit_id) bsw/jbe@1309: populate_units_with_initiative_right_hash(self) bsw/jbe@1309: return self.__units_with_initiative_right_hash[unit_id] and true or false bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: function Member.object_get:has_initiative_right() bsw/jbe@1309: populate_units_with_initiative_right_hash(self) bsw/jbe@1309: for k, v in pairs(self.__units_with_initiative_right_hash) do bsw/jbe@1309: return true bsw/jbe@1309: end bsw/jbe@1309: return false bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local function populate_units_with_voting_right_hash(self) bsw@547: if not self.__units_with_voting_right_hash then bsw@547: local privileges = Privilege:new_selector() bsw@547: :add_where{ "member_id = ?", self.id } bsw@547: :add_where("voting_right") bsw@547: :exec() bsw@547: self.__units_with_voting_right_hash = {} bsw@551: for i, privilege in ipairs(privileges) do bsw@551: self.__units_with_voting_right_hash[privilege.unit_id] = true bsw@551: end bsw@547: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: function Member.object_get:has_voting_right() bsw/jbe@1309: populate_units_with_voting_right_hash(self) bsw/jbe@1309: for k, v in pairs(self.__units_with_voting_right_hash) do bsw/jbe@1309: return true bsw/jbe@1309: end bsw/jbe@1309: return false bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: function Member.object:has_voting_right_for_unit_id(unit_id) bsw/jbe@1309: populate_units_with_voting_right_hash(self) bsw@547: return self.__units_with_voting_right_hash[unit_id] and true or false jbe@326: end bsw@525: bsw@894: function Member.object:has_polling_right_for_unit_id(unit_id) bsw@894: if not self.__units_with_polling_right_hash then bsw@894: local privileges = Privilege:new_selector() bsw@894: :add_where{ "member_id = ?", self.id } bsw@894: :add_where("polling_right") bsw@894: :exec() bsw@894: self.__units_with_polling_right_hash = {} bsw@894: for i, privilege in ipairs(privileges) do bsw@894: self.__units_with_polling_right_hash[privilege.unit_id] = true bsw@894: end bsw@894: end bsw@894: return self.__units_with_polling_right_hash[unit_id] and true or false bsw@894: end bsw@894: bsw@525: function Member.object:get_delegatee_member(unit_id, area_id, issue_id) bsw@525: local selector = Member:new_selector() bsw@525: if unit_id then bsw@525: selector:join("delegation", nil, { "delegation.trustee_id = member.id AND delegation.scope = 'unit' AND delegation.unit_id = ? AND delegation.truster_id = ?", unit_id, self.id }) bsw@525: end bsw@525: selector:optional_object_mode() bsw@525: return selector:exec() bsw@533: end bsw@929: bsw@1088: function Member.object:delete() bsw@1088: db:query{ "SELECT delete_member(?)", self.id } bsw@1088: end bsw/jbe@1309: bsw/jbe@1309: function Member.object_get:display_name() bsw/jbe@1309: if self.identification then bsw/jbe@1309: return self.identification bsw/jbe@1309: elseif self.name then bsw/jbe@1309: return self.name bsw/jbe@1309: else bsw/jbe@1309: return "Member #" .. self.id bsw/jbe@1309: end bsw/jbe@1309: end