WIP Add member/contributor status to users
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Râu Cao 2025-05-27 13:32:58 +04:00
parent 4bf6985b87
commit 393f85e45c
Signed by: raucao
GPG Key ID: 37036C356E56CC51
9 changed files with 116 additions and 30 deletions

View File

@ -4,13 +4,19 @@ class Admin::UsersController < Admin::BaseController
# GET /admin/users
def index
ldap = LdapService.new
@ou = Setting.primary_domain
@pagy, @users = pagy(User.where(ou: @ou).order(cn: :asc))
ldap = LdapService.new
ou = Setting.primary_domain
@admins = ldap.search_users(:admin, true, :cn)
@contributors = ldap.search_users(:memberStatus, :contributor, :cn)
@sustainers = ldap.search_users(:memberStatus, :sustainer, :cn)
@pagy, @users = pagy(User.where(ou: ou).order(cn: :asc))
@stats = {
users_confirmed: User.where(ou: @ou).confirmed.count,
users_pending: User.where(ou: @ou).pending.count
users_confirmed: User.where(ou: ou).confirmed.count,
users_pending: User.where(ou: ou).pending.count,
users_contributing: @contributors.size,
users_paying: @sustainers.size
}
end

View File

@ -0,0 +1,10 @@
module Settings
module MemberSettings
extend ActiveSupport::Concern
included do
field :member_default_status, type: :string,
default: ENV["MEMBER_DEFAULT_STATUS"].presence
end
end
end

View File

@ -163,7 +163,21 @@ class User < ApplicationRecord
def ldap_entry(reload: false)
return @ldap_entry if defined?(@ldap_entry) && !reload
@ldap_entry = ldap.fetch_users(uid: self.cn, ou: self.ou).first
@ldap_entry = ldap.fetch_users(cn: self.cn).first
end
def add_to_ldap_array(attr_key, ldap_attr, value)
current_entries = ldap_entry[attr_key.to_sym] || []
new_entries = Array(value).map(&:to_s)
entries = (current_entries + new_entries).uniq.sort
ldap.replace_attribute(dn, ldap_attr.to_sym, entries)
end
def remove_from_ldap_array(attr_key, ldap_attr, value)
current_entries = ldap_entry[attr_key.to_sym] || []
entries_to_remove = Array(value).map(&:to_s)
entries = (current_entries - entries_to_remove).uniq.sort
ldap.replace_attribute(dn, ldap_attr.to_sym, entries)
end
def display_name
@ -220,21 +234,39 @@ class User < ApplicationRecord
end
def enable_service(service)
current_services = services_enabled
new_services = Array(service).map(&:to_s)
services = (current_services + new_services).uniq.sort
ldap.replace_attribute(dn, :serviceEnabled, services)
add_to_ldap_array :services_enabled, :serviceEnabled, service
ldap_entry(reload: true)[:services_enabled]
end
def disable_service(service)
current_services = services_enabled
disabled_services = Array(service).map(&:to_s)
services = (current_services - disabled_services).uniq.sort
ldap.replace_attribute(dn, :serviceEnabled, services)
remove_from_ldap_array :services_enabled, :serviceEnabled, service
ldap_entry(reload: true)[:services_enabled]
end
def disable_all_services
ldap.delete_attribute(dn,:service)
ldap.delete_attribute(dn, :serviceEnabled)
end
def member_status
ldap_entry[:member_status] || []
end
def add_member_status(status)
add_to_ldap_array :member_status, :memberStatus, status
ldap_entry(reload: true)[:member_status]
end
def remove_member_status(status)
remove_from_ldap_array :member_status, :memberStatus, status
ldap_entry(reload: true)[:member_status]
end
def is_contributing_member?
member_status.map(&:to_sym).include?(:contributor)
end
def is_paying_member?
member_status.map(&:to_sym).include?(:sustainer)
end
private

View File

@ -50,19 +50,17 @@ class LdapService < ApplicationService
end
def fetch_users(args={})
if args[:ou]
treebase = "ou=#{args[:ou]},cn=users,#{ldap_suffix}"
else
treebase = ldap_config["base"]
end
attributes = %w[
dn cn uid mail displayName admin serviceEnabled
dn cn uid mail displayName admin serviceEnabled memberStatus
mailRoutingAddress mailpassword nostrKey pgpKey
]
filter = Net::LDAP::Filter.eq("uid", args[:uid] || "*")
filter = Net::LDAP::Filter.eq('objectClass', 'person') &
Net::LDAP::Filter.eq("cn", args[:cn] || "*")
entries = client.search(base: treebase, filter: filter, attributes: attributes)
entries = client.search(
base: ldap_config["base"], filter: filter,
attributes: attributes
)
entries.sort_by! { |e| e.cn[0] }
entries = entries.collect do |e|
{
@ -71,6 +69,7 @@ class LdapService < ApplicationService
display_name: e.try(:displayName) ? e.displayName.first : nil,
admin: e.try(:admin) ? 'admin' : nil,
services_enabled: e.try(:serviceEnabled),
member_status: e.try(:memberStatus),
email_maildrop: e.try(:mailRoutingAddress),
email_password: e.try(:mailpassword),
nostr_key: e.try(:nostrKey) ? e.nostrKey.first : nil,
@ -79,10 +78,20 @@ class LdapService < ApplicationService
end
end
def search_users(search_attr, value, return_attr)
filter = Net::LDAP::Filter.eq('objectClass', 'person') &
Net::LDAP::Filter.eq(search_attr.to_s, value.to_s) &
Net::LDAP::Filter.present('cn')
entries = client.search(
base: ldap_config["base"], filter: filter,
attributes: [return_attr]
)
entries.map { |entry| entry[return_attr].first }.compact
end
def fetch_organizations
attributes = %w{dn ou description}
filter = Net::LDAP::Filter.eq("objectClass", "organizationalUnit")
# filter = Net::LDAP::Filter.eq("objectClass", "*")
treebase = "cn=users,#{ldap_suffix}"
entries = client.search(base: treebase, filter: filter, attributes: attributes)

View File

@ -13,6 +13,16 @@
title: 'Pending',
value: @stats[:users_pending],
) %>
<%= render QuickstatsItemComponent.new(
type: :number,
title: 'Contributors',
value: @stats[:users_contributing],
) %>
<%= render QuickstatsItemComponent.new(
type: :number,
title: 'Sustainers',
value: @stats[:users_paying],
) %>
<% end %>
</section>
@ -29,8 +39,12 @@
<% @users.each do |user| %>
<tr>
<td><%= link_to(user.cn, admin_user_path(user.cn), class: 'ks-text-link') %></td>
<td><%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %></td>
<td><%= user.is_admin? ? badge("admin", :red) : "" %></td>
<td>
<%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %>
<%= @contributors.include?(user.cn) ? badge("contributor", :green) : "" %>
<%= @sustainers.include?(user.cn) ? badge("sustainer", :green) : "" %>
</td>
<td><%= @admins.include?(user.cn) ? badge("admin", :red) : "" %></td>
</tr>
<% end %>
</tbody>

View File

@ -32,6 +32,13 @@
<th>Roles</th>
<td><%= @user.is_admin? ? badge("admin", :red) : "—" %></td>
</tr>
<tr>
<th>Status</th>
<td>
<%= @user.is_contributing_member? ? badge("contributor", :green) : "" %>
<%= @user.is_paying_member? ? badge("sustainer", :green) : "" %>
</td>
</tr>
<tr>
<th>Invited by</th>
<td>

View File

@ -21,7 +21,7 @@ namespace :ldap do
desc "Add custom attributes to schema"
task add_custom_attributes: :environment do |t, args|
%w[ admin service_enabled nostr_key pgp_key ].each do |name|
%w[ admin service_enabled member_status nostr_key pgp_key ].each do |name|
Rake::Task["ldap:modify_ldap_schema"].invoke(name, "add")
Rake::Task['ldap:modify_ldap_schema'].reenable
end
@ -29,7 +29,7 @@ namespace :ldap do
desc "Delete custom attributes from schema"
task delete_custom_attributes: :environment do |t, args|
%w[ admin service_enabled nostr_key pgp_key ].each do |name|
%w[ admin service_enabled member_status nostr_key pgp_key ].each do |name|
Rake::Task["ldap:modify_ldap_schema"].invoke(name, "delete")
Rake::Task['ldap:modify_ldap_schema'].reenable
end

View File

@ -0,0 +1,8 @@
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( 1.3.6.1.4.1.61554.1.1.2.1.3
NAME 'memberStatus'
DESC 'Current member/contributor status'
EQUALITY caseExactMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )

View File

@ -154,7 +154,7 @@ RSpec.describe User, type: :model do
it "removes all services from the LDAP entry" do
expect_any_instance_of(LdapService).to receive(:delete_attribute)
.with(dn, :service).and_return(true)
.with(dn, :serviceEnabled).and_return(true)
user.disable_all_services
end