1 Commits

Author SHA1 Message Date
0bd77bc37a WIP Add service accounts and ACIs
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-28 10:57:12 +04:00
14 changed files with 41 additions and 193 deletions

View File

@@ -1,7 +1,7 @@
class CreateLdapUserJob < ApplicationJob class CreateLdapUserJob < ApplicationJob
queue_as :default queue_as :default
def perform(username:, domain:, email:, hashed_pw:, confirmed: false) def perform(username, domain, email, hashed_pw)
dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org" dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org"
attr = { attr = {
objectclass: ["top", "account", "person", "extensibleObject"], objectclass: ["top", "account", "person", "extensibleObject"],
@@ -12,10 +12,6 @@ class CreateLdapUserJob < ApplicationJob
userPassword: hashed_pw userPassword: hashed_pw
} }
if confirmed
attr[:serviceEnabled] = Setting.default_services
end
ldap_client.add(dn: dn, attributes: attr) ldap_client.add(dn: dn, attributes: attr)
end end

View File

@@ -206,9 +206,4 @@ class Setting < RailsSettings::Base
# #
# field :email_imap_port, type: :string, # field :email_imap_port, type: :string,
# default: ENV["EMAIL_IMAP_PORT"].presence || 993 # default: ENV["EMAIL_IMAP_PORT"].presence || 993
def self.default_services
# TODO Make configurable from respective service settings page
%w[ discourse gitea mastodon mediawiki xmpp ]
end
end end

View File

@@ -93,7 +93,9 @@ class User < ApplicationRecord
LdapManager::UpdateEmail.call(dn: self.dn, address: self.email) LdapManager::UpdateEmail.call(dn: self.dn, address: self.email)
else else
# E-Mail from signup confirmed (i.e. account activation) # E-Mail from signup confirmed (i.e. account activation)
enable_default_services
# TODO Make configurable, only activate globally enabled services
enable_service %w[ discourse gitea mediawiki xmpp ]
# TODO enable in development when we have easy setup of ejabberd etc. # TODO enable in development when we have easy setup of ejabberd etc.
return if Rails.env.development? || !Setting.ejabberd_enabled? return if Rails.env.development? || !Setting.ejabberd_enabled?
@@ -131,7 +133,7 @@ class User < ApplicationRecord
def mastodon_address def mastodon_address
return nil unless Setting.mastodon_enabled? return nil unless Setting.mastodon_enabled?
"#{self.cn.gsub("-", "_")}@#{Setting.mastodon_address_domain}" "#{self.cn}@#{Setting.mastodon_address_domain}"
end end
def valid_attribute?(attribute_name) def valid_attribute?(attribute_name)
@@ -139,10 +141,6 @@ class User < ApplicationRecord
self.errors[attribute_name].blank? self.errors[attribute_name].blank?
end end
def enable_default_services
enable_service Setting.default_services
end
def ln_create_invoice(payload) def ln_create_invoice(payload)
lndhub = Lndhub.new lndhub = Lndhub.new
lndhub.authenticate self lndhub.authenticate self

View File

@@ -35,15 +35,11 @@ class CreateAccount < ApplicationService
@invitation.update! invited_user_id: user_id, used_at: DateTime.now @invitation.update! invited_user_id: user_id, used_at: DateTime.now
end end
# TODO move to confirmation
# (and/or add email_confirmed to entry and use in login filter)
def add_ldap_document def add_ldap_document
hashed_pw = Devise.ldap_auth_password_builder.call(@password) hashed_pw = Devise.ldap_auth_password_builder.call(@password)
CreateLdapUserJob.perform_later( CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
username: @username,
domain: @domain,
email: @email,
hashed_pw: hashed_pw,
confirmed: @confirmed
)
end end
def create_lndhub_account(user) def create_lndhub_account(user)

View File

@@ -16,8 +16,8 @@
<p> <p>
There's something to do for everyone, especially non-programmers! For There's something to do for everyone, especially non-programmers! For
example, we need more help with graphics, UI/UX design, and example, we need more help with graphics, UI/UX design, and
content/copywriting. Also, testing any of our software and reporting content/copywriting. We also need moderators for social media. And beta
issues you encounter along the way is very valuable. testers for our software. The list doesn't end there.
</p> </p>
<p> <p>
A good way to get started is to join one of our A good way to get started is to join one of our
@@ -43,7 +43,7 @@
</p> </p>
<p> <p>
We have run two 6-month trials so far, with the next trial period We have run two 6-month trials so far, with the next trial period
starting sometime in Q2 2024. Watch your email for notifications about it! starting sometime in Q1 2024. Watch your email for notifications about it!
</p> </p>
</section> </section>
<% end %> <% end %>

View File

@@ -98,17 +98,7 @@
description: "The official Web app", description: "The official Web app",
icon_path: "/img/logos/icon_mastodon-2.svg", icon_path: "/img/logos/icon_mastodon-2.svg",
links: [ links: [
["Launch", "https://kosmos.social"], ["Launch", "https://kosmos.social"]
["GitHub", "https://github.com/mastodon/mastodon"]
]
) %>
<%= render AppInfoComponent.new(
name: "Phanpy",
description: " A slick, feature-rich Web app for mobile and desktop",
icon_path: "/img/logos/icon_phanpy.svg",
links: [
["Launch", "https://phanpy.social"],
["GitHub", "https://github.com/cheeaun/phanpy"]
] ]
) %> ) %>
<%= render AppInfoComponent.new( <%= render AppInfoComponent.new(
@@ -160,15 +150,6 @@
["Google Play", "https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"] ["Google Play", "https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"]
] ]
) %> ) %>
<%= render AppInfoComponent.new(
name: "Phanpy",
description: " A slick, feature-rich Web app for mobile and desktop",
icon_path: "/img/logos/icon_phanpy.svg",
links: [
["Launch", "https://phanpy.social"],
["GitHub", "https://github.com/cheeaun/phanpy"]
]
) %>
</div> </div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel"> <div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new( <%= render AppInfoComponent.new(
@@ -199,15 +180,6 @@
["App Store", "https://apps.apple.com/app/mammoth-for-mastodon/id1667573899"] ["App Store", "https://apps.apple.com/app/mammoth-for-mastodon/id1667573899"]
] ]
) %> ) %>
<%= render AppInfoComponent.new(
name: "Phanpy",
description: " A slick, feature-rich Web app for mobile and desktop",
icon_path: "/img/logos/icon_phanpy.svg",
links: [
["Launch", "https://phanpy.social"],
["GitHub", "https://github.com/cheeaun/phanpy"]
]
) %>
</div> </div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel"> <div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new( <%= render AppInfoComponent.new(

View File

@@ -2,4 +2,3 @@
:queues: :queues:
- default - default
- mailers - mailers
- remotestorage

View File

@@ -19,6 +19,18 @@ namespace :ldap do
}, true }, true
end end
# TODO
desc "Add application account to directory"
task add_application_account: :environment do |t, args|
# Add uid=service,ou=kosmos.org,cn=applications,dc=kosmos,dc=org with userPassword
end
# TODO
desc "Add application ACI/permissions for OU, i.e. read/search users"
task add_application_account: :environment do |t, args|
# (target="ldap:///cn=*,ou=#{ou},cn=users,#{ldap_suffix}")(targetattr="cn || sn || uid || mail || userPassword || nsRole || objectClass") (version 3.0; acl "service-#{ou.gsub(".", "-")}-read-search"; allow (read,search) userdn="ldap:///uid=service,ou=#{ou},cn=applications,#{ldap_suffix}";)
end
desc "Add custom attributes to schema" desc "Add custom attributes to schema"
task add_custom_attributes: :environment do |t, args| task add_custom_attributes: :environment do |t, args|
%w[ admin service_enabled nostr_key ].each do |name| %w[ admin service_enabled nostr_key ].each do |name|

View File

@@ -1,67 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="54"
height="54"
viewBox="0 0 54 54"
version="1.1"
xml:space="preserve"
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"
id="svg4"
sodipodi:docname="icon_phanpy.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:serif="http://www.serif.com/"><defs
id="defs4" /><sodipodi:namedview
id="namedview4"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="16.359375"
inkscape:cx="26.192932"
inkscape:cy="24.542502"
inkscape:window-width="2160"
inkscape:window-height="1281"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<rect
id="Logo-simple"
serif:id="Logo simple"
x="0"
y="0"
width="63.993999"
height="63.993999"
style="fill:none" />
<g
id="Logo-simple1"
serif:id="Logo simple"
transform="translate(-5.123639,-4.9968626)">
<g
id="g4">
<path
d="m 37.774,11.471 c 14.639,3.752 19.034,16.557 15.889,31.304 -0.696,3.261 -2.563,6.661 -6.356,8.693 -3.204,1.717 -8.07,2.537 -15.338,0.55 0,0 -9.634,-2.404 -9.634,-2.404 C 11.651,46.992 8.378,38.733 10.027,31.823 13.654,16.622 25.57,8.343 37.774,11.471 Z"
style="fill:#a4bff7"
id="path1" />
<path
d="m 36.76,15.429 c 12.289,3.15 15.547,14.114 12.907,26.493 -0.947,4.44 -4.937,9.365 -16.664,6.143 L 23.319,45.648 C 15.465,43.725 12.789,37.848 14.001,32.771 17.017,20.132 26.612,12.828 36.76,15.429 Z"
style="fill:#d8e7fe"
id="path2" />
<path
d="m 27.471,24.991 c -1.457,-0.698 -7.229,3.213 -7.663,8.926 -0.182,2.39 4.55,3.237 5.071,-0.169 0.725,-4.743 3.715,-8.218 2.592,-8.757 z"
style="fill:#6081e6"
id="path3" />
<path
d="m 38.217,26.996 c -2.083,0.327 -0.382,5.901 -0.595,10.727 -0.123,2.8 4.388,3.464 4.703,2.011 1.098,-5.073 -2.066,-13.058 -4.108,-12.738 z"
style="fill:#6081e6"
id="path4" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

4
schemas/ldap/aci.ldif Normal file
View File

@@ -0,0 +1,4 @@
dn: ou=kosmos.org,cn=users,dc=kosmos,dc=org
changetype: modify
add: aci
aci: (target="ldap:///cn=*,ou=kosmos.org,cn=users,dc=kosmos,dc=org")(targetattr="cn || sn || uid || mail || userPassword || serviceEnabled || displayName || jpegPhoto || nsRole || objectClass") (version 3.0; acl "service-kosmos-read-search"; allow (read,search) userdn="ldap:///uid=service,ou=kosmos.org,cn=applications,dc=kosmos,dc=org";)

View File

@@ -0,0 +1,4 @@
dn: ou=kosmos.org,cn=users,dc=kosmos,dc=org
changetype: modify
delete: aci
aci: (target="ldap:///cn=*,ou=kosmos.org,cn=users,dc=kosmos,dc=org")(targetattr="cn || sn || uid || mail || userPassword || nsRole || objectClass") (version 3.0; acl "service-kosmos-read-search"; allow (read,search) userdn="ldap:///uid=service,ou=kosmos.org,cn=applications,dc=kosmos,dc=org";)

View File

@@ -3,24 +3,12 @@ require 'rails_helper'
RSpec.describe CreateLdapUserJob, type: :job do RSpec.describe CreateLdapUserJob, type: :job do
let(:ldap_client_mock) { instance_double(Net::LDAP) } let(:ldap_client_mock) { instance_double(Net::LDAP) }
before do
allow_any_instance_of(described_class).to receive(:ldap_client).and_return(ldap_client_mock)
end
subject(:job) { subject(:job) {
described_class.perform_later( allow_any_instance_of(described_class).to receive(:ldap_client).and_return(ldap_client_mock)
username: 'halfinney', domain: 'kosmos.org',
email: 'halfinney@example.com',
hashed_pw: 'remember-remember-the-5th-of-november'
)
}
subject(:job_for_preconfirmed_account) {
described_class.perform_later( described_class.perform_later(
username: 'halfinney', domain: 'kosmos.org', 'halfinney', 'kosmos.org', 'halfinney@example.com',
email: 'halfinney@example.com', 'remember-remember-the-5th-of-november'
hashed_pw: 'remember-remember-the-5th-of-november',
confirmed: true
) )
} }
@@ -42,26 +30,6 @@ RSpec.describe CreateLdapUserJob, type: :job do
) )
end end
it "adds default services for pre-confirmed accounts" do
allow(ldap_client_mock).to receive(:add) # spy on mock
allow(Setting).to receive(:default_services).and_return(["xmpp", "discourse"])
perform_enqueued_jobs { job_for_preconfirmed_account }
expect(ldap_client_mock).to have_received(:add).with(
dn: "cn=halfinney,ou=kosmos.org,cn=users,dc=kosmos,dc=org",
attributes: {
objectclass: ["top", "account", "person", "extensibleObject"],
cn: "halfinney",
sn: "halfinney",
uid: "halfinney",
mail: "halfinney@example.com",
serviceEnabled: ["xmpp", "discourse"],
userPassword: "remember-remember-the-5th-of-november"
}
)
end
after do after do
clear_enqueued_jobs clear_enqueued_jobs
clear_performed_jobs clear_performed_jobs

View File

@@ -41,14 +41,6 @@ RSpec.describe User, type: :model do
expect(user.mastodon_address).to eq("jimmy@kosmos.social") expect(user.mastodon_address).to eq("jimmy@kosmos.social")
end end
end end
describe "username contains hyphen/dash" do
let(:jammy) { build :user, cn: "jammy-jellyfish", ou: "kosmos.org" }
it "returns the user address" do
expect(jammy.mastodon_address).to eq("jammy_jellyfish@kosmos.org")
end
end
end end
end end
@@ -155,7 +147,7 @@ RSpec.describe User, type: :model do
after { clear_enqueued_jobs } after { clear_enqueued_jobs }
it "enables default services" do it "enables default services" do
expect(user).to receive(:enable_service).with(%w[ discourse gitea mastodon mediawiki xmpp ]) expect(user).to receive(:enable_service).with(%w[ discourse gitea mediawiki xmpp ])
user.send :devise_after_confirmation user.send :devise_after_confirmation
end end

View File

@@ -53,32 +53,11 @@ RSpec.describe CreateAccount, type: :model do
expect(enqueued_jobs.size).to eq(1) expect(enqueued_jobs.size).to eq(1)
args = enqueued_jobs.first['arguments'][0] args = enqueued_jobs.first['arguments']
expect(args["username"]).to eq('halfinney') expect(args[0]).to eq('halfinney')
expect(args["domain"]).to eq('kosmos.org') expect(args[1]).to eq('kosmos.org')
expect(args["email"]).to eq('halfinney@example.com') expect(args[2]).to eq('halfinney@example.com')
expect(args["hashed_pw"]).to match(/^{SSHA512}.{171}=/) expect(args[3]).to match(/^{SSHA512}.{171}=/)
end
after do
clear_enqueued_jobs
end
end
describe "#add_ldap_document for pre-confirmed account" do
include ActiveJob::TestHelper
let(:service) { CreateAccount.new(account: {
username: 'halfinney',
email: 'halfinney@example.com',
password: 'remember-remember-the-5th-of-november',
confirmed: true
})}
it "enqueues a job to create the LDAP user document" do
service.send(:add_ldap_document)
args = enqueued_jobs.first['arguments'][0]
expect(args["confirmed"]).to be(true)
end end
after do after do