jabber_admin 0.1.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.editorconfig +30 -0
- data/.gitignore +4 -2
- data/.rubocop.yml +35 -0
- data/.simplecov +5 -0
- data/.travis.yml +8 -6
- data/.yardopts +4 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +4 -2
- data/Makefile +108 -0
- data/README.md +145 -21
- data/Rakefile +5 -3
- data/bin/config.rb +11 -0
- data/bin/console +3 -3
- data/docker-compose.yml +15 -0
- data/jabber_admin.gemspec +34 -27
- data/lib/jabber_admin.rb +97 -34
- data/lib/jabber_admin/api_call.rb +110 -20
- data/lib/jabber_admin/commands.rb +6 -12
- data/lib/jabber_admin/commands/ban_account.rb +11 -10
- data/lib/jabber_admin/commands/create_room.rb +15 -10
- data/lib/jabber_admin/commands/create_room_with_opts.rb +20 -14
- data/lib/jabber_admin/commands/get_vcard.rb +84 -0
- data/lib/jabber_admin/commands/muc_register_nick.rb +26 -0
- data/lib/jabber_admin/commands/register.rb +16 -11
- data/lib/jabber_admin/commands/registered_users.rb +18 -0
- data/lib/jabber_admin/commands/restart.rb +8 -5
- data/lib/jabber_admin/commands/send_direct_invitation.rb +19 -16
- data/lib/jabber_admin/commands/send_stanza.rb +21 -0
- data/lib/jabber_admin/commands/send_stanza_c2s.rb +28 -0
- data/lib/jabber_admin/commands/set_nickname.rb +22 -0
- data/lib/jabber_admin/commands/set_presence.rb +37 -0
- data/lib/jabber_admin/commands/set_room_affiliation.rb +26 -0
- data/lib/jabber_admin/commands/set_vcard.rb +68 -0
- data/lib/jabber_admin/commands/subscribe_room.rb +19 -12
- data/lib/jabber_admin/commands/unregister.rb +13 -7
- data/lib/jabber_admin/commands/unsubscribe_room.rb +10 -7
- data/lib/jabber_admin/configuration.rb +6 -1
- data/lib/jabber_admin/exceptions.rb +41 -0
- data/lib/jabber_admin/version.rb +19 -1
- metadata +120 -25
- data/lib/jabber_admin/initializer.rb +0 -5
data/Rakefile
CHANGED
data/bin/config.rb
ADDED
data/bin/console
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
|
4
|
-
require "jabber_admin"
|
4
|
+
require_relative 'config'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +10,5 @@ require "jabber_admin"
|
|
10
10
|
# require "pry"
|
11
11
|
# Pry.start
|
12
12
|
|
13
|
-
require
|
13
|
+
require 'irb'
|
14
14
|
IRB.start(__FILE__)
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
version: "3"
|
2
|
+
services:
|
3
|
+
jabber:
|
4
|
+
image: hausgold/ejabberd:18.01
|
5
|
+
network_mode: bridge
|
6
|
+
ports: ["4560", "5222", "5269", "5280", "5443"]
|
7
|
+
|
8
|
+
test:
|
9
|
+
image: ruby:2.5
|
10
|
+
network_mode: bridge
|
11
|
+
working_dir: /app
|
12
|
+
volumes:
|
13
|
+
- .:/app
|
14
|
+
links:
|
15
|
+
- jabber
|
data/jabber_admin.gemspec
CHANGED
@@ -1,42 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
|
-
lib = File.expand_path(
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
5
|
+
require 'jabber_admin/version'
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name
|
8
|
-
spec.version
|
9
|
-
spec.authors
|
10
|
-
spec.licenses
|
11
|
-
spec.email
|
12
|
-
|
13
|
-
spec.summary
|
14
|
-
spec.description
|
15
|
-
spec.homepage
|
16
|
-
|
17
|
-
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the
|
18
|
-
# to allow pushing to a single host or delete this
|
8
|
+
spec.name = 'jabber_admin'
|
9
|
+
spec.version = JabberAdmin::VERSION
|
10
|
+
spec.authors = ['Henning Vogt', 'Hermann Mayer']
|
11
|
+
spec.licenses = ['MIT']
|
12
|
+
spec.email = ['henning.vogt@hausgold.de', 'hermann.mayer@hausgold.de']
|
13
|
+
|
14
|
+
spec.summary = 'Library for the ejabberd RESTful admin API'
|
15
|
+
spec.description = 'Library for the ejabberd RESTful admin API'
|
16
|
+
spec.homepage = 'https://github.com/hausgold/jabber_admin'
|
17
|
+
|
18
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the
|
19
|
+
# 'allowed_push_host' to allow pushing to a single host or delete this
|
20
|
+
# section to allow pushing to any host.
|
19
21
|
if spec.respond_to?(:metadata)
|
20
|
-
spec.metadata[
|
22
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
21
23
|
else
|
22
|
-
raise
|
23
|
-
|
24
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
25
|
+
'public gem pushes.'
|
24
26
|
end
|
25
27
|
|
26
|
-
spec.
|
28
|
+
spec.required_ruby_version = '~> 2.5'
|
29
|
+
|
30
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
31
|
f.match(%r{^(test|spec|features)/})
|
28
32
|
end
|
29
|
-
spec.bindir =
|
33
|
+
spec.bindir = 'exe'
|
30
34
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
-
spec.require_paths = [
|
35
|
+
spec.require_paths = ['lib']
|
32
36
|
|
37
|
+
spec.add_dependency 'activesupport', '>= 5.2.0'
|
33
38
|
spec.add_dependency 'rest-client', '~> 2.0', '>= 2.0.2'
|
34
|
-
spec.add_dependency "activesupport", ">= 5"
|
35
|
-
|
36
|
-
spec.required_ruby_version = '>= 2.2'
|
37
39
|
|
38
|
-
spec.add_development_dependency
|
39
|
-
spec.add_development_dependency
|
40
|
-
spec.add_development_dependency
|
41
|
-
spec.add_development_dependency
|
40
|
+
spec.add_development_dependency 'bundler', '>= 1.16', '< 3'
|
41
|
+
spec.add_development_dependency 'irb', '~> 1.2'
|
42
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
43
|
+
spec.add_development_dependency 'rspec', '~> 3.9'
|
44
|
+
spec.add_development_dependency 'rubocop', '~> 0.93'
|
45
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.43'
|
46
|
+
spec.add_development_dependency 'simplecov', '< 0.18'
|
47
|
+
spec.add_development_dependency 'vcr', '~> 6.0'
|
48
|
+
spec.add_development_dependency 'webmock', '~> 3.0'
|
42
49
|
end
|
data/lib/jabber_admin.rb
CHANGED
@@ -1,61 +1,124 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'active_support/inflector'
|
4
|
+
require 'json'
|
5
|
+
require 'pathname'
|
6
|
+
require 'rest-client'
|
5
7
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
8
|
+
require 'jabber_admin/exceptions'
|
9
|
+
require 'jabber_admin/configuration'
|
10
|
+
require 'jabber_admin/commands'
|
11
|
+
require 'jabber_admin/api_call'
|
12
|
+
require 'jabber_admin/version'
|
11
13
|
|
12
|
-
|
13
|
-
# Jabber Admin Library
|
14
|
+
# jabber_admin
|
14
15
|
#
|
15
|
-
# allows making API calls to the ejabberd RESTful admin backend
|
16
|
-
#
|
16
|
+
# This gem allows making API calls to the ejabberd RESTful admin backend. We
|
17
|
+
# support a bunch of predefined commands out of the box, just have a look at
|
18
|
+
# the +lib/jabber_admin/commands/+ directory or the readme file.
|
17
19
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# (
|
20
|
+
# All predefined commands can be called via +JabberAdmin.COMMAND!+ or via
|
21
|
+
# +JabberAdmin.COMMAND+. The bang variant checks the result of the command
|
22
|
+
# (successful, or not) and raise a subclass of +JabberAdmin::Exception+ in case
|
23
|
+
# of issues. The non-bang variant just sends the commands in a fire and forget
|
24
|
+
# manner.
|
21
25
|
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
26
|
+
# When you're missing a command you want to use, you can use the
|
27
|
+
# +JabberAdmin::ApiCall+ class directly. It allows you to easily fulfill your
|
28
|
+
# custom needs with the power of error handling (if you like).
|
29
|
+
#
|
30
|
+
# You can also use your custom command directly on the +JabberAdmin+ module, in
|
31
|
+
# both banged and non-banged versions and we pass them as a shortcut to a new
|
32
|
+
# +JabberAdmin::ApiCall+ instance.
|
33
|
+
#
|
34
|
+
# @example Configure jabber_admin gem
|
35
|
+
# JabberAdmin.configure do |config|
|
36
|
+
# # The ejabberd REST API endpoint as a full URL.
|
37
|
+
# # Take care of the path part, because this is individually
|
38
|
+
# # configured on ejabberd. (See: https://bit.ly/2rBxatJ)
|
39
|
+
# config.url = 'http://jabber.local/api'
|
40
|
+
# # Provide here the full user JID in order to authenticate as
|
41
|
+
# # a administrator.
|
42
|
+
# config.username = 'admin@jabber.local'
|
43
|
+
# # The password of the administrator account.
|
44
|
+
# config.password = 'password'
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# @example Restart the ejabberd service
|
25
48
|
# JabberAdmin.restart!
|
26
49
|
#
|
50
|
+
# @example Register a new user to the XMPP service
|
51
|
+
# JabberAdmin.register! user: 'peter@example.com', password: '123'
|
52
|
+
#
|
53
|
+
# @example Delete a user from the XMPP service, in fire and forget manner
|
54
|
+
# JabberAdmin.unregister user: 'peter@example.com'
|
27
55
|
module JabberAdmin
|
28
56
|
class << self
|
29
57
|
attr_writer :configuration
|
30
58
|
end
|
31
59
|
|
32
|
-
#
|
60
|
+
# A simple getter to the global JabberAdmin configuration structure.
|
61
|
+
#
|
62
|
+
# @return [JabberAdmin::Configuration] the global JabberAdmin configuration
|
33
63
|
def self.configuration
|
34
|
-
@configuration ||=
|
64
|
+
@configuration ||= Configuration.new
|
35
65
|
end
|
36
66
|
|
37
|
-
# Class method to set and change the global configuration
|
67
|
+
# Class method to set and change the global configuration. This is just a
|
68
|
+
# tapped variant of the +.configuration+ method.
|
38
69
|
#
|
39
|
-
# @
|
40
|
-
#
|
41
|
-
# config.api_host = 'xmpp.example.com'
|
42
|
-
# config.admin = 'admin'
|
43
|
-
# config.password = 'password'
|
44
|
-
# end
|
70
|
+
# @yield [configuration]
|
71
|
+
# @yieldparam [JabberAdmin::Configuration] configuration
|
45
72
|
def self.configure
|
46
73
|
yield(configuration)
|
47
74
|
end
|
48
75
|
|
49
|
-
|
50
|
-
|
51
|
-
|
76
|
+
# Allow an easy to use DSL on the +JabberAdmin+ module. We support predefined
|
77
|
+
# (known) commands and unknown ones in bang and non-bang variants. This
|
78
|
+
# allows maximum flexibility to the user. The bang versions perform the
|
79
|
+
# response checks and raise in case of issues. The non-bang versions skip
|
80
|
+
# this checks. For unknown commands the +JabberAdmin::ApiCall+ is directly
|
81
|
+
# utilized with the method name as command. (Without the trailling bang, when
|
82
|
+
# it is present)
|
83
|
+
#
|
84
|
+
# @param method [Symbol, String, #to_s] the name of the command to run
|
85
|
+
# @param args all additional payload to pass down to the API call
|
86
|
+
# @return [RestClient::Response] the actual response of the command
|
87
|
+
def self.method_missing(method, *args)
|
88
|
+
predefined_command(method).call(predefined_callable(method), *args)
|
52
89
|
rescue NameError
|
53
|
-
|
90
|
+
predefined_callable(method).call(method.to_s.chomp('!'), *args)
|
54
91
|
end
|
55
92
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
93
|
+
# Try to find the given name as a predefined command. When there is no such
|
94
|
+
# predefined command, we raise a +NameError+.
|
95
|
+
#
|
96
|
+
# @param name [Symbol, String, #to_s] the command name to lookup
|
97
|
+
# @return [Class] the predefined command class constant
|
98
|
+
def self.predefined_command(name)
|
99
|
+
# Remove bangs and build the camel case variant
|
100
|
+
"JabberAdmin::Commands::#{name.to_s.chomp('!').camelize}".constantize
|
101
|
+
end
|
102
|
+
|
103
|
+
# Generate a matching API call wrapper for the given command name. When we
|
104
|
+
# have to deal with a bang version, we pass the bang down to the API call
|
105
|
+
# instance. Otherwise we just run the regular +#perform+ method on the API
|
106
|
+
# call instance.
|
107
|
+
#
|
108
|
+
# @param name [Symbol, String, #to_s] the command name to match
|
109
|
+
# @return [Proc] the API call wrapper
|
110
|
+
def self.predefined_callable(name)
|
111
|
+
method = name.to_s.end_with?('!') ? 'perform!' : 'perform'
|
112
|
+
proc { |*args| ApiCall.send(method, *args) }
|
113
|
+
end
|
114
|
+
|
115
|
+
# We support all methods if you ask for. This is our dynamic command approach
|
116
|
+
# here to support predefined and custom commands in the same namespace.
|
117
|
+
#
|
118
|
+
# @param method [String] the method to lookup
|
119
|
+
# @param include_private [Boolean] allow the lookup of private methods
|
120
|
+
# @return [Boolean] always +true+
|
121
|
+
def self.respond_to_missing?(_method, _include_private = false)
|
122
|
+
true
|
60
123
|
end
|
61
124
|
end
|
@@ -1,42 +1,132 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JabberAdmin
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
# Handles the communication with the API
|
4
|
+
# Handles a single communication with the API. An instance persists the
|
5
|
+
# response when performed once. So you can get the response multiple times,
|
6
|
+
# without repeating the request.
|
8
7
|
class ApiCall
|
9
|
-
attr_reader :command, :payload
|
10
|
-
|
11
|
-
def self.perform(command, payload = {})
|
12
|
-
new(command, payload).perform
|
13
|
-
end
|
8
|
+
attr_reader :command, :payload, :check_res_body
|
14
9
|
|
15
|
-
|
10
|
+
# Setup a new API call instance with the given command and the given
|
11
|
+
# request payload.
|
12
|
+
#
|
13
|
+
# @param command [String] the command to execute
|
14
|
+
# @param check_res_body [Boolean] whenever to check the response body
|
15
|
+
# @param payload the request payload, empty by default
|
16
|
+
def initialize(command, check_res_body: true, **payload)
|
16
17
|
@command = command
|
17
18
|
@payload = payload
|
19
|
+
@check_res_body = check_res_body
|
18
20
|
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
# The resulting URL of the API call. This URL is constructed with the
|
23
|
+
# +JabberAdmin.configuration.url+ as base and the command name as the
|
24
|
+
# suffix. The configuration is allowed to end with trailling slash, or not.
|
25
|
+
#
|
26
|
+
# @return [String] the API call URL
|
27
|
+
def url
|
28
|
+
"#{JabberAdmin.configuration.url.strip.chomp('/')}/#{@command}"
|
24
29
|
end
|
25
30
|
|
26
|
-
|
27
|
-
|
31
|
+
# This method compose the actual request, perfoms it and stores the
|
32
|
+
# response to the instance. Additional calls to this method will not
|
33
|
+
# repeat the request, but will deliver the response directly.
|
34
|
+
#
|
35
|
+
# @return [RestClient::Response] the response of the API call
|
28
36
|
def response
|
29
|
-
@
|
37
|
+
@response ||= RestClient::Request.execute(
|
30
38
|
method: :post,
|
31
39
|
url: url,
|
32
|
-
user: JabberAdmin.configuration.
|
40
|
+
user: JabberAdmin.configuration.username,
|
33
41
|
password: JabberAdmin.configuration.password,
|
34
42
|
payload: payload.to_json
|
35
43
|
)
|
44
|
+
rescue RestClient::Exception => e
|
45
|
+
@response = e.response
|
36
46
|
end
|
37
47
|
|
38
|
-
|
39
|
-
|
48
|
+
# Check if the response was successful. Otherwise raise exceptions with
|
49
|
+
# +JabberAdmin::Exception+ as base type.
|
50
|
+
#
|
51
|
+
# @raise JabberAdmin::ApiError
|
52
|
+
# @raise JabberAdmin::CommandError
|
53
|
+
#
|
54
|
+
# rubocop:disable Metrics/AbcSize because its the bundled check logic
|
55
|
+
# rubocop:disable Metrics/MethodLength dito
|
56
|
+
def check_response
|
57
|
+
# The REST API responds a 404 status code when the command is not known.
|
58
|
+
if response.code == 404
|
59
|
+
raise UnknownCommandError.new("Command '#{command}' is not known",
|
60
|
+
response)
|
61
|
+
end
|
62
|
+
|
63
|
+
# In case we send commands with missing data or any other validation
|
64
|
+
# issues, the REST API will respond with a 400 Bad Request status
|
65
|
+
# code.
|
66
|
+
raise CommandError.new('Invalid arguments for command', response) \
|
67
|
+
if response.code == 400
|
68
|
+
|
69
|
+
# Looks like the ejabberd REST API is returning 200 OK in case the
|
70
|
+
# request was valid and permitted. But it does not indicate that the
|
71
|
+
# request was successful handled. This is indicated on the response body
|
72
|
+
# as a one (1) or a zero (0). (0 on success, 1 otherwise)
|
73
|
+
raise RequestError.new('Response code was not 200', response) \
|
74
|
+
unless response.code == 200
|
75
|
+
|
76
|
+
# Stop the check, when we should not check the response body
|
77
|
+
return unless check_res_body
|
78
|
+
|
79
|
+
# The command was not successful, for some reason. Unfortunately we do
|
80
|
+
# not get any further information here, which makes error debugging a
|
81
|
+
# struggle.
|
82
|
+
raise CommandError.new('Command was not successful', response) \
|
83
|
+
unless response.body == '0'
|
84
|
+
end
|
85
|
+
# rubocop:enable Metrics/AbcSize
|
86
|
+
# rubocop:enable Metrics/MethodLength
|
87
|
+
|
88
|
+
# Just a simple DSL wrapper for the response method.
|
89
|
+
#
|
90
|
+
# @return [RestClient::Response] the API call response
|
91
|
+
def perform
|
92
|
+
response
|
93
|
+
end
|
94
|
+
|
95
|
+
# Just a simple DSL wrapper for the response method. But this variant
|
96
|
+
# perfoms a response check which will raise excpetions when there are
|
97
|
+
# issues.
|
98
|
+
#
|
99
|
+
# @raise JabberAdmin::ApiError
|
100
|
+
# @raise JabberAdmin::CommandError
|
101
|
+
# @return [RestClient::Response] the API call response
|
102
|
+
def perform!
|
103
|
+
check_response
|
104
|
+
response
|
105
|
+
end
|
106
|
+
|
107
|
+
# A simple class level shortcut of the +perform+ method. This is just DSL
|
108
|
+
# code which accepts the same arguments as the instance initialize method.
|
109
|
+
# (+#new+)
|
110
|
+
#
|
111
|
+
# @param command [String] the command to execute
|
112
|
+
# @param payload [Hash] the request payload, empty by default
|
113
|
+
# @return [RestClient::Response] the API call response
|
114
|
+
def self.perform(*args)
|
115
|
+
new(*args).perform
|
116
|
+
end
|
117
|
+
|
118
|
+
# A simple class level shortcut of the +perform!+ method. This is just DSL
|
119
|
+
# code which accepts the same arguments as the instance initialize method.
|
120
|
+
# (+#new+)
|
121
|
+
#
|
122
|
+
# @param command [String] the command to execute
|
123
|
+
# @param payload [Hash] the request payload, empty by default
|
124
|
+
# @return [RestClient::Response] the API call response
|
125
|
+
#
|
126
|
+
# @raise JabberAdmin::ApiError
|
127
|
+
# @raise JabberAdmin::CommandError
|
128
|
+
def self.perform!(*args)
|
129
|
+
new(*args).perform!
|
40
130
|
end
|
41
131
|
end
|
42
132
|
end
|
@@ -1,17 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'jabber_admin/commands/ban_account'
|
4
|
-
require 'jabber_admin/commands/create_room'
|
5
|
-
require 'jabber_admin/commands/create_room_with_opts'
|
6
|
-
require 'jabber_admin/commands/register'
|
7
|
-
require 'jabber_admin/commands/restart'
|
8
|
-
require 'jabber_admin/commands/send_direct_invitation'
|
9
|
-
require 'jabber_admin/commands/subscribe_room'
|
10
|
-
require 'jabber_admin/commands/unsubscribe_room'
|
11
|
-
require 'jabber_admin/commands/unregister'
|
12
|
-
|
13
|
-
##
|
14
|
-
# Contains alle commands that are supported
|
15
3
|
module JabberAdmin
|
4
|
+
# Contains all predefined commands that are supported.
|
16
5
|
module Commands; end
|
17
6
|
end
|
7
|
+
|
8
|
+
# Require all commands from the commands subfolder
|
9
|
+
Dir[Pathname.new(__dir__).join('commands', '**', '*.rb')].sort.each do |file|
|
10
|
+
require file
|
11
|
+
end
|