cadenero 0.0.2.a3 → 0.0.2.b1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +4 -3
  3. data/app/controllers/cadenero/v1/account/sessions_controller.rb +3 -1
  4. data/app/controllers/cadenero/v1/account/users_controller.rb +8 -2
  5. data/app/controllers/cadenero/v1/accounts_controller.rb +13 -1
  6. data/app/extenders/middleware/robustness.rb +19 -0
  7. data/app/models/cadenero/member.rb +1 -0
  8. data/app/models/cadenero/user.rb +2 -0
  9. data/app/models/cadenero/v1/account.rb +16 -1
  10. data/app/serializers/cadenero/account_serializer.rb +1 -0
  11. data/app/serializers/cadenero/user_serializer.rb +1 -0
  12. data/config/initializers/apartment.rb +4 -1
  13. data/config/initializers/warden/strategies/password.rb +1 -1
  14. data/config/routes.rb +1 -0
  15. data/lib/cadenero/constraints/subdomain_required.rb +2 -0
  16. data/lib/cadenero/engine.rb +1 -1
  17. data/lib/cadenero/testing_support/subdomain_helpers.rb +15 -0
  18. data/lib/cadenero/version.rb +1 -1
  19. data/lib/cadenero.rb +2 -0
  20. data/lib/generators/cadenero/install_generator.rb +17 -0
  21. data/spec/dummy/log/development.log +209 -0
  22. data/spec/dummy/log/test.log +26783 -5135
  23. data/spec/dummy/tmp/ember-rails/ember.js +1693 -676
  24. data/spec/features/users/sign_in_spec.rb +13 -3
  25. data/spec/models/cadenero/account_spec.rb +31 -1
  26. data/spec/spec_helper.rb +1 -0
  27. metadata +11 -44
  28. data/app/helpers/cadenero/application_helper.rb +0 -4
  29. data/app/helpers/cadenero/v1/accounts_helper.rb +0 -4
  30. data/app/helpers/cadenero/v1/users_helper.rb +0 -4
  31. data/spec/features/cadenero/account_spec.rb +0 -23
  32. data/spec/support/subdomain_helpers.rb +0 -8
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ N2Y4NzhjNmE2MmVlYTlkNjNjMWFlYzI4MTYwZTkwMTQyMDk1MzBjNQ==
5
+ data.tar.gz: !binary |-
6
+ MWVkNDAwNjIwMDEwNjM2YWYzZmNiOTQxNTdkZGE2OTJjMmU5YjQ2Mg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ Y2RiYTg0YTM0NzdiOGQwYWEzZWZjNGU3NGUwNzU5MjZhYmU0NjcxZTllY2I0
10
+ OWE2YTJkMmMzMDE0ZGFhNzY0N2YxMGQ1ZDg2Yjk1MWFiMjU0OGYzYjBlOTk4
11
+ ZWUzNmEyMjYwMDIyZmU0YjQ4MjgwM2I0NjcxZGIzOWFmYWE3OTA=
12
+ data.tar.gz: !binary |-
13
+ MjNlM2Y3YmEyZmQ2MjU0MzZlNmEwYzJlZTgwNGUxZTc3NWY5NGE1OGY4NjIy
14
+ NzYyOTlhYzAyOGFhMzhjZDBjN2M5NWExYjdkMDUyY2MxNTdiOTlkMGJmYjQ2
15
+ MzVjNzhjYTM4NzVjZjQzODdjYmU1ZDdlZDYyNWZjMWM5Yjg3NmE=
data/README.md CHANGED
@@ -6,7 +6,6 @@ By [![Agiltec Logo](https://launchrock-assets.s3.amazonaws.com/logo-files/Gpujzv
6
6
  [![Code Climate](https://codeclimate.com/github/AgilTec/cadenero.png)](https://codeclimate.com/github/AgilTec/cadenero)
7
7
  [![Coverage Status](https://coveralls.io/repos/AgilTec/cadenero/badge.png?branch=master)](https://coveralls.io/r/AgilTec/cadenero?branch=master)
8
8
  [![Dependency Status](https://gemnasium.com/AgilTec/cadenero.png)](https://gemnasium.com/AgilTec/cadenero)
9
- [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/AgilTec/cadenero/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
10
9
 
11
10
  Authentication Engine for Rails.API multitenant RESTful APIs based on Warden. It:
12
11
  * Is Racked based
@@ -28,7 +27,7 @@ Generate first your Rails.API app as usual using:
28
27
 
29
28
  In the `Gemfile` add the following lines:
30
29
  ```ruby
31
- gem 'cadenero', '~> 0.0.2.a3'
30
+ gem 'cadenero', '~> 0.0.2.b1'
32
31
  gem 'pg'
33
32
  ```
34
33
 
@@ -110,10 +109,12 @@ You can check them running:
110
109
  ```
111
110
  rake routes
112
111
  ```
112
+ ### Documentation
113
+ You can review the YARD docs in: http://rubydoc.info/github/AgilTec/cadenero/frames
113
114
 
114
115
  ### The Cadenero Task List
115
116
  - [x] Specs for the code 100% Coverage using BDD with [Rspec](https://github.com/rspec/rspec) and [Capybara](https://github.com/jnicklas/capybara)
116
- - [ ] Documatation for all the code
117
+ - [x] Documatation for all the code
117
118
  - [ ] Examples of use and demo
118
119
 
119
120
  ### Versions
@@ -1,6 +1,7 @@
1
1
  require_dependency "cadenero/application_controller"
2
- # COntroller for managing sessions for the API if you are using the :password Strategy
2
+ #NameSpace for the V1 API in Cadenero
3
3
  module Cadenero::V1
4
+ # Controller for managing sessions for the API if you are using the :password Strategy
4
5
  class Account::SessionsController < Cadenero::ApplicationController
5
6
  # create the session for the user using the password strategy and returning the user JSON
6
7
  def create
@@ -13,6 +14,7 @@ module Cadenero::V1
13
14
  end
14
15
  end
15
16
 
17
+ # destroy the session for the user using params id
16
18
  def delete
17
19
  user = Cadenero::User.find_by_id(params[:id])
18
20
  if user_signed_in?
@@ -7,21 +7,27 @@ require_dependency "cadenero/application_controller"
7
7
 
8
8
  module Cadenero
9
9
  module V1
10
+ # Controller for managing users for specific accounts
10
11
  class Account::UsersController < Cadenero::ApplicationController
11
12
  # Create a [Cadenero::User] based on the params sended by the client as a JSON with the user inrormation
12
13
  #
13
14
  # @example Posting the user data to be created in an account via the subdomain
14
- # post "http://#{account.subdomain}.example.com/v1/sign_up",
15
+ # post "http://#{account.subdomain}.example.com/v1/users",
15
16
  # user: { email: "user@example.com", password: "password", password_confirmation: "password" }
16
17
  #
17
18
  # @return render JSON of [Cadenero::User] created and the status 201 Created: The request has been
18
- # fulfilled and resulted in a new resource being created..
19
+ # fulfilled and resulted in a new resource being created.
19
20
  def create
20
21
  account = Cadenero::V1::Account.where(subdomain: request.subdomain).first
21
22
  @user = account.users.create(params[:user])
22
23
  force_authentication!(@user)
23
24
  render json: @user, status: :created
24
25
  end
26
+ # Send as JSON the user that match the params[:user]
27
+ def show
28
+ @user = account.users.where(params[:user]).first
29
+ render json: @user, status: :ok
30
+ end
25
31
  end
26
32
  end
27
33
  end
@@ -2,11 +2,23 @@ require_dependency "cadenero/application_controller"
2
2
 
3
3
  module Cadenero
4
4
  module V1
5
+ # Handles the Accounts for the multitenant. An account creates a subdomain in the server as a route
6
+ # the account has an owner that is the default admin for that account
5
7
  class AccountsController < Cadenero::ApplicationController
8
+ # For API consistency provides the example for creating a new account
6
9
  def new
7
10
  errors = %Q{Please sign up. posting the account json data as {account: { name: "Testy", subdomain: "test", owner_attributes: {email: "testy@example.com", password: "changeme", password_confirmation: "changeme"} }} to /v1/accounts/sign_up}
8
- render json: {errors: errors, links: "/v1/accounts/sign_up"}, status: 422
11
+ render json: {errors: errors, links: "/v1/accounts/sign_up"}, status: :unprocessable_entity
9
12
  end
13
+ # Create a [Cadenero::V1::Account] based on the params sended by the client as a JSON with the account inrormation
14
+ #
15
+ # @example Posting the account data to be created in a subdomain
16
+ # post "http://www.example.com/v1/accounts",
17
+ # account: { name: "Testy", subdomain: "test",
18
+ # owner_attributes: {email: "testy@example.com", password: "changeme", password_confirmation: "changeme"} }
19
+ #
20
+ # @return render JSON of [Cadenero::V1::Account] created and the status 201 Created: The request has been
21
+ # fulfilled and resulted in a new resource being created.
10
22
  def create
11
23
  @account = Cadenero::V1::Account.create_with_owner(params[:account])
12
24
  if @account.valid?
@@ -0,0 +1,19 @@
1
+ # Intercepts excpetion from the middlewares to handled those as JSON responses
2
+ class Robustness
3
+
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ # Rack API call for rescue from the exceptions
9
+ def call(env)
10
+ @app.call(env)
11
+ rescue Apartment::SchemaNotFound => ex
12
+ [422, { 'Content-Type' => 'application/json' }, [ {errors: {subdomain:["Invalid subdomain"]}}.to_json ] ] # suppose the message can be safely used
13
+ rescue SecurityError => ex
14
+ [403, { 'Content-Type' => 'application/json' }, [ ex.message ] ]
15
+ ensure
16
+ env['rack.errors'].write(ex.message) if ex
17
+ end
18
+
19
+ end
@@ -1,4 +1,5 @@
1
1
  module Cadenero
2
+ # Defines that a Cadenero::User is member of an Cadenero::V1::Account
2
3
  class Member < ActiveRecord::Base
3
4
  belongs_to :account, :class_name => "Cadenero::V1::Account"
4
5
  belongs_to :user, :class_name => "Cadenero::User"
@@ -1,4 +1,5 @@
1
1
  module Cadenero
2
+ # Defines a user of one or more accounts for the multitenant Rails App
2
3
  class User < ActiveRecord::Base
3
4
  attr_accessible :email, :password, :password_confirmation
4
5
  has_secure_password
@@ -6,6 +7,7 @@ module Cadenero
6
7
  has_many :members, class_name: "Cadenero::Member"
7
8
  has_many :memberships, through: :members, source: :account
8
9
 
10
+ # Obtain the authentication_token from the account to be use for the User
9
11
  def auth_token
10
12
  accounts[0].authentication_token if accounts[0]
11
13
  end
@@ -1,4 +1,5 @@
1
1
  module Cadenero::V1
2
+ # Defines a subdomain with a default admin (owner) as a tenant in the Rails App
2
3
  class Account < ActiveRecord::Base
3
4
  belongs_to :owner, :class_name => "Cadenero::User"
4
5
  has_many :members, :class_name => "Cadenero::Member"
@@ -9,7 +10,7 @@ module Cadenero::V1
9
10
  validates :subdomain, :presence => true, :uniqueness => true
10
11
  validates :owner, :presence => true
11
12
 
12
- # Creates an accout and assign the provided [Cadenero::User] as owner to the account
13
+ # Creates an account and assign the provided [Cadenero::User] as owner to the account
13
14
  # @param [Hash] params list
14
15
  # @example
15
16
  # Example for the params JSON: {name: "Testy", subdomain: "test",
@@ -25,6 +26,20 @@ module Cadenero::V1
25
26
  account
26
27
  end
27
28
 
29
+ # Gets the account for the specified subdomain and guards errors
30
+ # @param [String] params subdomain
31
+ # @example
32
+ # get_by_subdomain("www")
33
+ # @return the [Cadenero::V1::Account] for that subdomain
34
+ def self.get_by_subdomain(subdomain)
35
+ account = find_by_subdomain(subdomain)
36
+ if account
37
+ account
38
+ else
39
+ raise Apartment::SchemaNotFound, "Subdomain is not valid"
40
+ end
41
+ end
42
+
28
43
  # Create a database schema using the subdomain
29
44
  def create_schema
30
45
  Apartment::Database.create(subdomain)
@@ -1,4 +1,5 @@
1
1
  module Cadenero
2
+ # JSON Serializaer for the Cadenero::V1::Account Model
2
3
  class AccountSerializer < ActiveModel::Serializer
3
4
  embed :ids
4
5
  attributes :id, :name, :subdomain, :authentication_token
@@ -1,4 +1,5 @@
1
1
  module Cadenero
2
+ # JSON Serializaer for the Cadenero::User Model
2
3
  class UserSerializer < ActiveModel::Serializer
3
4
  embed :ids
4
5
  attributes :id, :email, :auth_token
@@ -1,4 +1,7 @@
1
- Rails.application.config.middleware.use 'Apartment::Elevators::Subdomain'
1
+ require File.expand_path('../../../app/extenders/middleware/robustness', __FILE__)
2
+
3
+ Rails.application.config.middleware.use(Robustness)
4
+ Rails.application.config.middleware.use(Apartment::Elevators::Subdomain)
2
5
 
3
6
 
4
7
  Apartment.configure do |config|
@@ -17,7 +17,7 @@ Warden::Strategies.add(:password) do
17
17
  end
18
18
 
19
19
  def authenticate!
20
- account = Cadenero::V1::Account.find_by_subdomain(subdomain)
20
+ account = Cadenero::V1::Account.get_by_subdomain(subdomain)
21
21
  if account
22
22
  u = account.users.find_by_email(json_params["user"]["email"])
23
23
  if u.nil? || u.blank?
data/config/routes.rb CHANGED
@@ -8,6 +8,7 @@ Cadenero::Engine.routes.draw do
8
8
  post '/sessions', :to => "sessions#create", default: :json
9
9
  delete '/sessions', :to => "sessions#delete", default: :json
10
10
  post '/users', :to => "users#create", default: :json
11
+ get '/users', :to => "users#show", :as => :users, default: :json
11
12
  end
12
13
  end
13
14
  post '/accounts', :to => "accounts#create", :as => :accounts, default: :json
@@ -1,5 +1,7 @@
1
1
  module Cadenero
2
+ # Constraints for the routes
2
3
  module Constraints
4
+ # For the routes require that a subdomain is defined
3
5
  class SubdomainRequired
4
6
  def self.matches?(request)
5
7
  request.subdomain.present? && request.subdomain != "www"
@@ -1,6 +1,6 @@
1
1
  require 'warden'
2
2
  module Cadenero
3
-
3
+ # Defines Cadenero as a Rails Engine using the passworf strategy as default for Warden
4
4
  class Engine < ::Rails::Engine
5
5
  isolate_namespace Cadenero
6
6
  config.middleware.use Warden::Manager do |manager|
@@ -0,0 +1,15 @@
1
+ module Cadenero
2
+ # Helper Methods for testing
3
+ module TestingSupport
4
+ # RSpec Helper for subdomains
5
+ module SubdomainHelpers
6
+ # To be use for RSpec features for defining and account with a subdomain visible for Capybara
7
+ def within_account_subdomain
8
+ let(:subdomain_url) { "http://#{account.subdomain}.example.com" }
9
+ before { Capybara.default_host = subdomain_url }
10
+ after { Capybara.default_host = "http://example.com" }
11
+ yield
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module Cadenero
2
- VERSION = "0.0.2.a3"
2
+ VERSION = "0.0.2.b1" # Current VERSION of Cadenero
3
3
  end
data/lib/cadenero.rb CHANGED
@@ -34,10 +34,12 @@ module Cadenero
34
34
  :default_user_password
35
35
 
36
36
  class << self
37
+ # @return the base path for the Cadenero named routes
37
38
  def base_path
38
39
  @@base_path ||= Rails.application.routes.named_routes[:cadenero].path
39
40
  end
40
41
 
42
+ # defines which class will be used as User Class in Cadenero
41
43
  def user_class
42
44
  if @@user_class.is_a?(Class)
43
45
  raise "You can no longer set Cadenero.user_class to be a class. Please use a string instead."
@@ -1,6 +1,8 @@
1
1
  require 'rails/generators'
2
2
  module Cadenero
3
+ # Bootstrap Cadenero in a new Rails App
3
4
  module Generators
5
+ # Cadenero generator to be used as `rails-api g cadenero:install`
4
6
  class InstallGenerator < Rails::Generators::Base
5
7
  class_option "user-class", :type => :string
6
8
  class_option "default-account-name", :type => :string
@@ -13,6 +15,7 @@ module Cadenero
13
15
  source_root File.expand_path("../install/templates", __FILE__)
14
16
  desc "Used to install Cadenero"
15
17
 
18
+ # Copy the Cadenero Migrations in the New App
16
19
  def install_migrations
17
20
  puts "Copying over Cadenero migrations..."
18
21
  Dir.chdir(Rails.root) do
@@ -20,12 +23,14 @@ module Cadenero
20
23
  end
21
24
  end
22
25
 
26
+ # Defines which class will be used as User Class for Cadenero
23
27
  def determine_user_class
24
28
  @user_class = options["user-class"].presence ||
25
29
  ask("What will be the name for your user class? [User]").presence ||
26
30
  'User'
27
31
  end
28
32
 
33
+ # Defines which helper will be used as User Class Helper for Cadenero and inject that in the application_controller.rb
29
34
  def determine_current_user_helper
30
35
  current_user_helper = options["current-user-helper"].presence ||
31
36
  ask("What will be the current_user helper called in your app? [current_user]").presence ||
@@ -47,30 +52,35 @@ module Cadenero
47
52
 
48
53
  end
49
54
 
55
+ # Define which will be the Root Account for the Multitnant Rails App
50
56
  def determine_default_account_name
51
57
  Cadenero.default_account_name = options["default-account-name"].presence ||
52
58
  ask("What will be the name for the default account? [Root Account]").presence ||
53
59
  'Root Account'
54
60
  end
55
61
 
62
+ # Define which will be the root subdomain for the Multitnant Rails App. (Default: www)
56
63
  def determine_default_account_subdomain
57
64
  Cadenero.default_account_subdomain = options["default-account-subdomain"].presence ||
58
65
  ask("What will be the subdomain for the default account? [www]").presence ||
59
66
  'www'
60
67
  end
61
68
 
69
+ # Define which will be the root owner for the defaul accout Multitnant Rails App.
62
70
  def determine_default_user_email
63
71
  Cadenero.default_user_email = options["default-user-email"].presence ||
64
72
  ask("What will be the email for the default user owner of the default account? [testy@example.com]").presence ||
65
73
  'testy@example.com'
66
74
  end
67
75
 
76
+ # Define which will be the password for the root owner for the defaul accout Multitnant Rails App.
68
77
  def determine_default_user_password
69
78
  Cadenero.default_user_password = options["default-user-password"].presence ||
70
79
  ask("What will be the password for the default user owner of the default account? [change-me]").presence ||
71
80
  'change-me'
72
81
  end
73
82
 
83
+ # Create the Cadenero initilizar based on the Template.
74
84
  def add_cadenero_initializer
75
85
  path = "#{Rails.root}/config/initializers/cadenero.rb"
76
86
  if File.exists?(path)
@@ -82,6 +92,7 @@ module Cadenero
82
92
  end
83
93
  end
84
94
 
95
+ # Run the Cadenero Migrations in the New App
85
96
  def run_migrations
86
97
  unless options["no-migrate"]
87
98
  puts "Running rake db:migrate"
@@ -89,6 +100,7 @@ module Cadenero
89
100
  end
90
101
  end
91
102
 
103
+ # Seed the databas using the options selected as defaults
92
104
  def seed_database
93
105
  puts "default_account_name: #{Cadenero.default_account_name}"
94
106
  puts "default_account_subdomain: #{Cadenero.default_account_subdomain}"
@@ -102,6 +114,7 @@ module Cadenero
102
114
  end
103
115
  end
104
116
 
117
+ # Injects the code in the `routes.rb` for mounting the Cadenero Engine
105
118
  def mount_engine
106
119
  puts "Mounting Cadenero::Engine at \"/\" in config/routes.rb..."
107
120
  insert_into_file("#{Rails.root}/config/routes.rb", :after => /routes.draw.do\n/) do
@@ -115,6 +128,7 @@ module Cadenero
115
128
  end
116
129
  end
117
130
 
131
+ # Wrap-ups the installation giving appropiate messages
118
132
  def finished
119
133
  output = "\n\n" + ("*" * 53)
120
134
  output += %Q{\nDone! Cadenero has been successfully installed. Yaaaaay! He will keep the intoxicated JSON's out
@@ -146,16 +160,19 @@ output += step("`rake db:migrate` was run, running all the migrations against yo
146
160
 
147
161
  private
148
162
 
163
+ # Keep track of the step number for the installation process
149
164
  def step(words)
150
165
  @step ||= 0
151
166
  @step += 1
152
167
  "#{@step}) #{words}\n"
153
168
  end
154
169
 
170
+ # @return the user_class
155
171
  def user_class
156
172
  @user_class
157
173
  end
158
174
 
175
+ # keep track of the migration numbers
159
176
  def next_migration_number
160
177
  last_migration = Dir[Rails.root + "db/migrate/*.rb"].sort.last.split("/").last
161
178
  current_migration_number = /^(\d+)_/.match(last_migration)[1]