4 Commits

Author SHA1 Message Date
Râu Cao
537e1a4774 Update database schema (from Rails upgrade)
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2022-12-05 13:36:49 +01:00
Râu Cao
c3b9ff8b4a Add LDAP service and seed task 2022-12-05 13:36:33 +01:00
Râu Cao
93d56f79d5 Add config and documentation for running dirsrv with Docker 2022-12-05 13:35:30 +01:00
Râu Cao
1a30345f46 Add byebug for debugging in development 2022-12-05 13:20:47 +01:00
10 changed files with 218 additions and 35 deletions

View File

@@ -1,4 +1,11 @@
LDAP_HOST=localhost
LDAP_PORT=389
LDAP_ADMIN_PASSWORD=passthebutter
LDAP_SUFFIX="dc=kosmos,dc=org"
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
BTCPAY_API_URL='http://localhost:23001/api/v1'
LNDHUB_API_URL='http://localhost:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'

View File

@@ -50,6 +50,7 @@ group :development, :test do
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
gem 'rspec-rails'
gem "byebug", "~> 11.1"
end
group :development do

View File

@@ -71,6 +71,7 @@ GEM
bcrypt (3.1.18)
bindex (0.8.1)
builder (3.2.4)
byebug (11.1.3)
capybara (3.38.0)
addressable
matrix
@@ -157,6 +158,7 @@ GEM
matrix (0.4.2)
method_source (1.0.0)
mini_mime (1.1.2)
mini_portile2 (2.8.0)
minitest (5.16.3)
net-imap (0.3.1)
net-protocol
@@ -168,6 +170,9 @@ GEM
net-smtp (0.3.3)
net-protocol
nio4r (2.5.8)
nokogiri (1.13.9)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.9-x86_64-linux)
racc (~> 1.4)
orm_adapter (0.5.0)
@@ -259,6 +264,8 @@ GEM
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
sqlite3 (1.5.4)
mini_portile2 (~> 2.8.0)
sqlite3 (1.5.4-x86_64-linux)
stimulus-rails (1.2.1)
railties (>= 6.0.0)
@@ -298,6 +305,7 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
byebug (~> 11.1)
capybara
cssbundling-rails
database_cleaner

View File

@@ -31,10 +31,31 @@ Running all specs:
bundle exec rspec
### Docker (Compose)
There is a working Dockr Compose config file, which allows you to spin up both
an app server for Rails as well as a local 389ds (LDAP) server.
By default, `docker-compose up` will only start the LDAP server, listening on
port 389 on your machine. Uncomment other services in `docker-compose.yml`.
### LDAP server
TODO make it easy to run a local Kosmos LDAP server for development, without
manual LDIF imports etc. (or provide a staging instance)
See the previous section for quickly spinning up an LDAP server with Docker (or
edit your environment configuration to use an existing one).
After creating the Docker container for the first time (or after deleting it),
you need to run the following command once, in order to create the dirsrv
back-end:
docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"
Now you can seed the back-end with data using this Rails task:
bundle exec rails ldap:seed
The seeds task will first delete any existing entries in the directory tree
("dc=kosmos,dc=org"), and then create our example/development entries.
## Documentation

View File

@@ -18,7 +18,7 @@ class CreateLdapUserJob < ApplicationJob
def ldap_client
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
port: ldap_config['port'],
encryption: ldap_config['ssl'],
# encryption: ldap_config['ssl'],
auth: {
method: :simple,
username: ldap_config['admin_user'],

View File

@@ -0,0 +1,111 @@
class LdapService < ApplicationService
def initialize
@suffix = ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
end
def add_entry(dn, attrs, interactive=false)
puts "Adding entry: #{dn}" if interactive
res = ldap_client.add dn: dn, attributes: attrs
puts res.inspect if interactive && !res
res
end
def delete_entry(dn, interactive=false)
puts "Deleting entry: #{dn}" if interactive
res = ldap_client.delete dn: dn
puts res.inspect if interactive && !res
res
end
def delete_all_entries
if Rails.env.production?
raise "Mass deletion of entries not allowed in production"
end
filter = Net::LDAP::Filter.eq("objectClass", "*")
entries = ldap_client.search(base: @suffix, filter: filter, attributes: %w{dn})
entries.sort_by!{ |e| e.dn.length }.reverse!
entries.each do |e|
delete_entry e.dn, true
end
end
def fetch_users(args={})
if args[:ou]
treebase = "ou=#{args[:ou]},cn=users,#{@suffix}"
else
treebase = ldap_config["base"]
end
attributes = %w{dn cn uid mail admin}
filter = Net::LDAP::Filter.eq("uid", "*")
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
entries.sort_by! { |e| e.cn[0] }
entries = entries.collect do |e|
{
uid: e.uid.first,
mail: e.try(:mail) ? e.mail.first : nil,
admin: e.try(:admin) ? 'admin' : nil
# password: e.userpassword.first
}
end
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,#{@suffix}"
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
entries.sort_by! { |e| e.ou[0] }
entries = entries.collect do |e|
{
dn: e.dn,
ou: e.ou.first,
description: e.try(:description) ? e.description.first : nil,
}
end
end
def add_organization(ou, description, interactive=false)
dn = "ou=#{ou},cn=users,#{@suffix}"
aci = <<-EOS
(target="ldap:///cn=*,ou=#{ou},cn=users,#{@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,#{@suffix}";)
EOS
attrs = {
objectClass: ["top", "organizationalUnit"],
description: description,
ou: ou,
aci: aci
}
add_entry dn, attrs, interactive
end
private
def ldap_client
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
port: ldap_config['port'],
# TODO has to be :simple_tls if TLS is enabled
# encryption: ldap_config['ssl'],
auth: {
method: :simple,
username: ldap_config['admin_user'],
password: ldap_config['admin_password']
}
end
def ldap_config
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
end
end

View File

@@ -29,7 +29,7 @@ development:
host: <%= ENV["LDAP_HOST"] || "localhost" %>
port: <%= ENV["LDAP_PORT"] || "389" %>
attribute: cn
base: ou=kosmos.org,cn=users,dc=kosmos,dc=org
base: <%= ENV["LDAP_BASE"] || "ou=kosmos.org,cn=users,dc=kosmos,dc=org" %>
admin_user: "cn=Directory Manager"
admin_password: <%= ENV["LDAP_ADMIN_PASSWORD"] %>
ssl: <%= ENV["LDAP_USE_TLS"] || "false" %>

View File

@@ -2,25 +2,24 @@
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `rails
# db:schema:load`. When creating a new database, `rails db:schema:load` tends to
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_11_20_010540) do
ActiveRecord::Schema[7.0].define(version: 2021_11_20_010540) do
create_table "donations", force: :cascade do |t|
t.integer "user_id"
t.integer "amount_sats"
t.integer "amount_eur"
t.integer "amount_usd"
t.string "public_name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "paid_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "paid_at", precision: nil
t.index ["user_id"], name: "index_donations_on_user_id"
end
@@ -28,22 +27,24 @@ ActiveRecord::Schema.define(version: 2021_11_20_010540) do
t.string "token"
t.integer "user_id"
t.integer "invited_user_id"
t.datetime "used_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "used_at", precision: nil
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["invited_user_id"], name: "index_invitations_on_invited_user_id"
t.index ["user_id"], name: "index_invitations_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "cn"
t.string "ou"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "reset_password_sent_at", precision: nil
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.datetime "confirmed_at", precision: nil
t.datetime "confirmation_sent_at", precision: nil
t.string "unconfirmed_email"
t.text "ln_login_ciphertext"
t.text "ln_password_ciphertext"

View File

@@ -3,22 +3,32 @@ services:
image: 4teamwork/389ds:latest
volumes:
- ./tmp/389ds:/data
ports:
- "389:3389"
environment:
DS_DM_PASSWORD: passthebutter
SUFFIX_NAME: "dc=kosmos,dc=org"
web:
build: .
tty: true
command: bash -c "rm -f tmp/pids/server.pid && bin/dev"
volumes:
- .:/akkounts
ports:
- "3000:3000"
environment:
RAILS_ENV: development
LDAP_HOST: ldap
LDAP_PORT: 3389
LDAP_ADMIN_PASSWORD: passthebutter
LDAP_USE_TLS: "false"
depends_on:
- ldap
# phpldapadmin:
# image: osixia/phpldapadmin:0.9.0
# ports:
# - "8389:80"
# environment:
# PHPLDAPADMIN_HTTPS: false
# PHPLDAPADMIN_LDAP_HOSTS: "#PYTHON2BASH:[{'ldap': [{'server': [{'tls': False}, {'port': 3389}]}, {'login': [{'bind_id': 'cn=Directory Manager'}, {'bind_pass': 'passthebutter'}]}]}]"
# PHPLDAPADMIN_LDAP_CLIENT_TLS: false
# web:
# build: .
# tty: true
# command: bash -c "sleep 5 && rm -f tmp/pids/server.pid && bin/dev"
# volumes:
# - .:/akkounts
# ports:
# - "3000:3000"
# environment:
# RAILS_ENV: development
# LDAP_HOST: ldap
# LDAP_PORT: 3389
# LDAP_ADMIN_PASSWORD: passthebutter
# LDAP_USE_TLS: "false"
# depends_on:
# - ldap

24
lib/tasks/ldap.rake Normal file
View File

@@ -0,0 +1,24 @@
namespace :ldap do
desc "Set up base entries for LDAP directory"
task seed: :environment do |t, args|
ldap = LdapService.new
ldap.delete_all_entries
ldap.add_entry "dc=kosmos,dc=org", {
dc: "kosmos", objectClass: ["top", "domain"]
}, true
ldap.add_entry "cn=users,dc=kosmos,dc=org", {
cn: "users", objectClass: ["top", "organizationalRole"]
}, true
ldap.add_organization "kosmos.org", "Kosmos", true
end
desc "List user domains/organizations"
task list_organizations: :environment do |t, args|
ldap = LdapService.new
orgs = ldap.fetch_organizations
puts orgs.inspect
end
end