jabber_admin 0.1.2 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +30 -0
  3. data/.gitignore +4 -2
  4. data/.rubocop.yml +35 -0
  5. data/.simplecov +5 -0
  6. data/.travis.yml +8 -6
  7. data/.yardopts +4 -0
  8. data/CHANGELOG.md +53 -0
  9. data/Gemfile +4 -2
  10. data/Makefile +108 -0
  11. data/README.md +145 -21
  12. data/Rakefile +5 -3
  13. data/bin/config.rb +11 -0
  14. data/bin/console +3 -3
  15. data/docker-compose.yml +15 -0
  16. data/jabber_admin.gemspec +34 -27
  17. data/lib/jabber_admin.rb +97 -34
  18. data/lib/jabber_admin/api_call.rb +110 -20
  19. data/lib/jabber_admin/commands.rb +6 -13
  20. data/lib/jabber_admin/commands/ban_account.rb +11 -10
  21. data/lib/jabber_admin/commands/create_room.rb +15 -10
  22. data/lib/jabber_admin/commands/create_room_with_opts.rb +20 -14
  23. data/lib/jabber_admin/commands/get_vcard.rb +84 -0
  24. data/lib/jabber_admin/commands/muc_register_nick.rb +26 -0
  25. data/lib/jabber_admin/commands/register.rb +16 -11
  26. data/lib/jabber_admin/commands/registered_users.rb +18 -0
  27. data/lib/jabber_admin/commands/restart.rb +8 -5
  28. data/lib/jabber_admin/commands/send_direct_invitation.rb +19 -16
  29. data/lib/jabber_admin/commands/send_stanza.rb +21 -0
  30. data/lib/jabber_admin/commands/send_stanza_c2s.rb +28 -0
  31. data/lib/jabber_admin/commands/set_nickname.rb +22 -0
  32. data/lib/jabber_admin/commands/set_presence.rb +37 -0
  33. data/lib/jabber_admin/commands/set_room_affiliation.rb +17 -12
  34. data/lib/jabber_admin/commands/set_vcard.rb +68 -0
  35. data/lib/jabber_admin/commands/subscribe_room.rb +19 -12
  36. data/lib/jabber_admin/commands/unregister.rb +13 -7
  37. data/lib/jabber_admin/commands/unsubscribe_room.rb +10 -7
  38. data/lib/jabber_admin/configuration.rb +6 -1
  39. data/lib/jabber_admin/exceptions.rb +41 -0
  40. data/lib/jabber_admin/version.rb +20 -1
  41. metadata +119 -25
  42. data/lib/jabber_admin/initializer.rb +0 -5
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ $VERBOSE = nil
4
+ require 'bundler/setup'
5
+ require 'jabber_admin'
6
+
7
+ JabberAdmin.configure do |conf|
8
+ conf.username = 'admin@jabber.local'
9
+ conf.password = 'defaultpw'
10
+ conf.url = 'http://jabber.local/api'
11
+ end
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
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 "irb"
13
+ require 'irb'
14
14
  IRB.start(__FILE__)
@@ -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
@@ -1,42 +1,49 @@
1
+ # frozen_string_literal: true
1
2
 
2
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "jabber_admin/version"
5
+ require 'jabber_admin/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "jabber_admin"
8
- spec.version = JabberAdmin::VERSION
9
- spec.authors = ["Henning Vogt"]
10
- spec.licenses = ['MIT']
11
- spec.email = ["henning.vogt@hausgold.de"]
12
-
13
- spec.summary = %q{Library for the Ejabberd RESTful admin API}
14
- spec.description = %q{Library for the Ejabberd RESTful admin API}
15
- spec.homepage = "https://github.com/hausgold/jabber_admin"
16
-
17
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
- # to allow pushing to a single host or delete this section to allow pushing to any host.
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["allowed_push_host"] = "https://rubygems.org"
22
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
21
23
  else
22
- raise "RubyGems 2.0 or newer is required to protect against " \
23
- "public gem pushes."
24
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
25
+ 'public gem pushes.'
24
26
  end
25
27
 
26
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
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 = "exe"
33
+ spec.bindir = 'exe'
30
34
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
- spec.require_paths = ["lib"]
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 "bundler", "~> 1.16"
39
- spec.add_development_dependency "rake", "~> 10.0"
40
- spec.add_development_dependency "rspec", "~> 3.0"
41
- spec.add_development_dependency "simplecov", "~> 0.15"
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
@@ -1,61 +1,124 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/inflector"
4
- require "rest-client"
3
+ require 'active_support/inflector'
4
+ require 'json'
5
+ require 'pathname'
6
+ require 'rest-client'
5
7
 
6
- require "jabber_admin/initializer"
7
- require "jabber_admin/configuration"
8
- require "jabber_admin/commands"
9
- require "jabber_admin/api_call"
10
- require "jabber_admin/version"
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
- # All supported commands are available in /lib/jabber_admin/commands/
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
- # Usage:
19
- # All commands can be called via JabberAdmin.[method_name]!
20
- # (Do not forget the bang at the end)
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
- # @example:
23
- # JabberAdmin.register!(user: 'peter', 'host': 'example.com', password: '123')
24
- # JabberAdmin.unregister!(user: 'peter', 'host': 'example.com')
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
- # @return [JabberAdmin::Configuration] The global JabberAdmin configuration
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 ||= JabberAdmin::Configuration.new
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
- # @example
40
- # JabberAdmin.configure do |config|
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
- def self.method_missing(method, *args, &block)
50
- command = "jabber_admin/commands/#{method[0..-2]}".classify.constantize
51
- args.empty? ? command.call : command.call(*args)
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
- super
90
+ predefined_callable(method).call(method.to_s.chomp('!'), *args)
54
91
  end
55
92
 
56
- def self.respond_to_missing?(method, include_private = false)
57
- "jabber_admin/commands/#{method[0..-2]}".classify.constantize && true
58
- rescue NameError
59
- super
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
- # Error that will be raised when API call is not successful
5
- class ApiError < StandardError; end
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
- def initialize(command, payload)
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
- def perform
21
- raise(ApiError, response.to_s) if response.code != 200
22
-
23
- response
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
- private
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
- @res ||= RestClient::Request.execute(
37
+ @response ||= RestClient::Request.execute(
30
38
  method: :post,
31
39
  url: url,
32
- user: JabberAdmin.configuration.admin,
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
- def url
39
- "#{JabberAdmin.configuration.api_host}/api/#{@command}"
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,18 +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
- require 'jabber_admin/commands/set_room_affiliation'
13
-
14
- ##
15
- # Contains alle commands that are supported
16
3
  module JabberAdmin
4
+ # Contains all predefined commands that are supported.
17
5
  module Commands; end
18
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