mumukit-auth 1.0.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 70c6f03db7fc8a236b7b46ca958514e61ff3dd13
4
- data.tar.gz: e2a30faa89265699b2ea9d3817caedb9fa215d52
3
+ metadata.gz: c6c6bdaf752ff0631203b9b75b9f6355e8221072
4
+ data.tar.gz: 39560f938b75e9cf21bf68332e84f7414c9d31c7
5
5
  SHA512:
6
- metadata.gz: f16261e01254897e2162a25782bfc83f8f4fb0eaf1ac61c398c661ed21f044a1b75ee2e7169c9facf963d4c8b4baaad9de4982d8a3a8eccdc042aeea6b44e4ed
7
- data.tar.gz: fccafb9f032c7e850c404c2b7142aad7e5f9f76a260dc8017edc4617388b6320f5b5f00f7712876a52e4cafee6325085e00f82833b85250713d82b146efbf6e5
6
+ metadata.gz: bfeede5ef70098672b9d5e0b9c9ea143bec35b238e4ebbdd783832d7c6adee6b487d986fd4453cb45e219c5117b35ed9f968ee85fd44f534286fa4648511876e
7
+ data.tar.gz: 1b8e40683bf497d548b6a3b1bb906a46092a0e30aa479906eda25e33c850947860e324c426ec56552965716d029590b1632bae8be789ff56c558c7d185a747db
data/lib/mumukit/auth.rb CHANGED
@@ -1,13 +1,15 @@
1
1
  require 'active_support/all'
2
2
  require 'mumukit/core'
3
3
 
4
+ require_relative './auth/array'
5
+ require_relative './auth/roles'
6
+ require_relative './auth/slug'
4
7
  require_relative './auth/version'
5
8
  require_relative './auth/exceptions'
6
9
  require_relative './auth/grant'
7
- require_relative './auth/metadata'
8
10
  require_relative './auth/token'
11
+ require_relative './auth/scope'
9
12
  require_relative './auth/permissions'
10
- require_relative './auth/user'
11
13
 
12
14
  require 'ostruct'
13
15
 
@@ -0,0 +1,6 @@
1
+ class Array
2
+ # Expands the array to length, filling the blanks with the element given
3
+ def pad_with(element, length)
4
+ self.fill(element, self.length, length - self.length)
5
+ end
6
+ end
@@ -4,7 +4,4 @@ module Mumukit::Auth
4
4
 
5
5
  class UnauthorizedAccessError < StandardError
6
6
  end
7
-
8
- class EmailNotRegistered < StandardError
9
- end
10
7
  end
@@ -1,15 +1,22 @@
1
+ class String
2
+ def to_mumukit_grant
3
+ Mumukit::Auth::Grant.parse self
4
+ end
5
+ end
6
+
7
+
1
8
  module Mumukit::Auth
2
9
  class Grant
3
10
  def as_json(options={})
4
11
  to_s
5
12
  end
6
13
 
7
- def [](resource)
8
- (self.class.slug? resource) ? allows?(resource) : access?(resource)
14
+ def to_mumukit_grant
15
+ self
9
16
  end
10
17
 
11
- def self.slug?(resource_identifier)
12
- /.*\/.*/.matches? resource_identifier
18
+ def ==(other)
19
+ other.class == self.class && to_s == other.to_s
13
20
  end
14
21
 
15
22
  def self.parse(pattern)
@@ -17,19 +24,15 @@ module Mumukit::Auth
17
24
  when '*' then
18
25
  AllGrant.new
19
26
  when /(.*)\/\*/
20
- OrgGrant.new($1)
27
+ FirstPartGrant.new($1)
21
28
  else
22
- SingleGrant.new(pattern)
29
+ SingleGrant.new(Slug.parse pattern)
23
30
  end
24
31
  end
25
32
  end
26
33
 
27
34
  class AllGrant < Grant
28
- def allows?(slug)
29
- true
30
- end
31
-
32
- def access?(organization)
35
+ def allows?(_resource_slug)
33
36
  true
34
37
  end
35
38
 
@@ -38,39 +41,32 @@ module Mumukit::Auth
38
41
  end
39
42
  end
40
43
 
41
- class SingleGrant < Grant
42
- def initialize(slug)
43
- @slug = slug
44
- end
45
-
46
- def allows?(slug)
47
- @slug == slug
44
+ class FirstPartGrant < Grant
45
+ def initialize(first)
46
+ @first = first
48
47
  end
49
48
 
50
- def access?(organization)
51
- @slug.split('/')[0] == organization
49
+ def allows?(resource_slug)
50
+ resource_slug.to_mumukit_slug.match_first @first
52
51
  end
53
52
 
54
53
  def to_s
55
- @slug
54
+ "#{@first}/*"
56
55
  end
57
56
  end
58
57
 
59
- class OrgGrant < Grant
60
- def initialize(org)
61
- @org = org
62
- end
63
-
64
- def allows?(slug)
65
- /^#{@org}\/.*/.matches? slug
58
+ class SingleGrant < Grant
59
+ def initialize(slug)
60
+ @slug = slug
66
61
  end
67
62
 
68
- def access?(organization)
69
- @org == organization
63
+ def allows?(resource_slug)
64
+ resource_slug = resource_slug.to_mumukit_slug
65
+ resource_slug.match_first(@slug.first) && resource_slug.match_second(@slug.second)
70
66
  end
71
67
 
72
68
  def to_s
73
- "#{@org}/*"
69
+ @slug.to_s
74
70
  end
75
71
  end
76
72
  end
@@ -1,58 +1,54 @@
1
- module Mumukit::Auth
2
- class Permissions
1
+ class Mumukit::Auth::Permissions
2
+ include Mumukit::Auth::Roles
3
3
 
4
- def initialize(grants)
5
- @grants = grants
6
- end
4
+ attr_accessor :scopes
7
5
 
8
- def protect!(slug)
9
- raise Mumukit::Auth::UnauthorizedAccessError.new(unauthorized_message(slug)) unless allows?(slug)
10
- end
6
+ def initialize(scopes={})
7
+ raise 'invalid scopes' if scopes.any? { |key, value| value.class != Mumukit::Auth::Scope }
11
8
 
12
- def allows?(slug)
13
- any_grant? { |grant| grant.allows? slug }
14
- end
15
-
16
- def access?(organization)
17
- any_grant? { |grant| grant.access? organization }
18
- end
9
+ @scopes = scopes
10
+ end
19
11
 
20
- def [](organization)
21
- any_grant? { |grant| grant[organization] }
22
- end
12
+ def has_permission?(role, resource_slug)
13
+ !!scope_for(role)&.allows?(resource_slug)
14
+ end
23
15
 
24
- def as_json
25
- to_s
26
- end
16
+ def has_role?(role)
17
+ scopes[role].present?
18
+ end
27
19
 
28
- def to_s
29
- @grants.map(&:to_s).uniq.join(':')
30
- end
20
+ def scope_for(role)
21
+ self.scopes[role]
22
+ end
31
23
 
32
- def present?
33
- to_s.present?
34
- end
24
+ def add_permission!(role, *grants)
25
+ self.scopes[role] ||= Mumukit::Auth::Scope.new
26
+ scope_for(role)&.add_grant! *grants
27
+ end
35
28
 
36
- def self.dump(permission)
37
- permission.to_s
38
- end
29
+ def remove_permission!(role, grant)
30
+ scope_for(role)&.remove_grant!(grant)
31
+ end
39
32
 
40
- def self.load(pattern)
41
- parse(pattern)
42
- end
33
+ def update_permission!(role, old_grant, new_grant)
34
+ remove_permission! role, old_grant
35
+ add_permission! role, new_grant
36
+ end
43
37
 
44
- def self.parse(pattern)
45
- new(pattern.split(':').map { |grant_pattern| Grant.parse(grant_pattern) })
46
- end
38
+ def as_json(options={})
39
+ scopes.as_json(options)
40
+ end
47
41
 
48
- private
42
+ def self.parse(hash)
43
+ new(Hash[hash.map { |role, grants| [role, Mumukit::Auth::Scope.parse(grants)] }])
44
+ end
49
45
 
50
- def any_grant?(&block)
51
- @grants.any?(&block)
52
- end
46
+ def self.load(json)
47
+ parse(JSON.parse(json))
48
+ end
53
49
 
54
- def unauthorized_message(slug)
55
- "Unauthorized access to #{slug}. Permissions are #{to_s}"
56
- end
50
+ def self.dump(user)
51
+ user.to_json
57
52
  end
53
+
58
54
  end
@@ -0,0 +1,12 @@
1
+ module Mumukit::Auth
2
+ module Roles
3
+ ROLES = [:student, :teacher, :headmaster, :writer, :editor, :janitor, :owner]
4
+
5
+ ROLES.each do |role|
6
+ define_method "#{role}?" do |scope|
7
+ has_permission? role.to_sym, scope
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,52 @@
1
+ module Mumukit::Auth
2
+ class Scope
3
+ attr_accessor :grants
4
+
5
+ def initialize(grants=[])
6
+ @grants = grants
7
+ end
8
+
9
+ def protect!(resource_slug)
10
+ raise Mumukit::Auth::UnauthorizedAccessError.new(unauthorized_message(resource_slug)) unless allows?(resource_slug)
11
+ end
12
+
13
+ def allows?(resource_slug)
14
+ any_grant? { |grant| grant.allows? resource_slug }
15
+ end
16
+
17
+ def add_grant!(*grants)
18
+ self.grants.push *grants.map(&:to_mumukit_grant)
19
+ end
20
+
21
+ def remove_grant!(grant)
22
+ grant = grant.to_mumukit_grant
23
+ self.grants.delete(grant)
24
+ end
25
+
26
+ def to_s
27
+ grants.map(&:to_s).join(':')
28
+ end
29
+
30
+ def present?
31
+ to_s.present?
32
+ end
33
+
34
+ def self.parse(string)
35
+ new(string.split(':').map(&:to_mumukit_grant))
36
+ end
37
+
38
+ def as_json(_options={})
39
+ to_s
40
+ end
41
+
42
+ private
43
+
44
+ def any_grant?(&block)
45
+ @grants.any?(&block)
46
+ end
47
+
48
+ def unauthorized_message(slug)
49
+ "Unauthorized access to #{slug}. Permissions are #{to_s}"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,71 @@
1
+ class String
2
+ def to_mumukit_slug
3
+ Mumukit::Auth::Slug.parse self
4
+ end
5
+ end
6
+
7
+ module Mumukit::Auth
8
+ class Slug
9
+ attr_accessor :first, :second
10
+
11
+ alias_method :organization, :first
12
+
13
+ alias_method :repository, :second
14
+ alias_method :course, :second
15
+ alias_method :content, :second
16
+
17
+ def initialize(first, second)
18
+ @first = first
19
+ @second = second
20
+ end
21
+
22
+ def match_first(first)
23
+ match self.first, first
24
+ end
25
+
26
+ def match_second(second)
27
+ match self.second, second
28
+ end
29
+
30
+ def ==(o)
31
+ self.class == o.class && to_s == o.to_s
32
+ end
33
+
34
+ def to_s
35
+ "#{first}/#{second}"
36
+ end
37
+
38
+ def to_mumukit_slug
39
+ self
40
+ end
41
+
42
+ def self.join(*parts)
43
+ raise 'Slugs must have up to two parts' if parts.length > 2
44
+ new(*parts.pad_with('_', 2))
45
+ end
46
+
47
+ def self.parse(slug)
48
+ validate_slug! slug
49
+
50
+ self.new *slug.split('/')
51
+ end
52
+
53
+ private
54
+
55
+ def match(pattern, part)
56
+ pattern == '_' || pattern == part
57
+ end
58
+
59
+ def self.validate_slug!(slug)
60
+ unless slug =~ /.*\/.*/
61
+ raise Mumukit::Auth::InvalidSlugFormatError, "Invalid slug: #{slug}. It must be in first/second format"
62
+ end
63
+ end
64
+ end
65
+
66
+ class InvalidSlugFormatError < StandardError
67
+ end
68
+ end
69
+
70
+
71
+
@@ -9,24 +9,23 @@ module Mumukit::Auth
9
9
  end
10
10
 
11
11
  def metadata
12
- @metadata ||= Mumukit::Auth::Metadata.new(jwt['app_metadata'] || {})
13
- end
14
-
15
- def permissions(app)
16
- metadata.permissions(app)
12
+ @metadata ||= jwt['metadata'] || {}
17
13
  end
18
14
 
19
15
  def verify_client!
20
16
  raise Mumukit::Auth::InvalidTokenError.new('aud mismatch') if Mumukit::Auth.config.client_id != jwt['aud']
21
17
  end
22
18
 
23
- def self.from_env(env)
19
+ def self.from_rack_env(env)
24
20
  new(env.dig('omniauth.auth', 'extra', 'raw_info') || {})
25
21
  end
26
22
 
27
- def self.decode_header(header)
28
- raise Mumukit::Auth::InvalidTokenError.new('missing authorization header') if header.nil?
29
- decode header.split(' ').last
23
+ def self.encode_dummy_auth_header(metadata)
24
+ 'dummy token ' + encode(metadata)
25
+ end
26
+
27
+ def self.encode(metadata)
28
+ JWT.encode({aud: Mumukit::Auth.config.client_id, metadata: metadata}, decoded_secret)
30
29
  end
31
30
 
32
31
  def self.decode(encoded)
@@ -35,35 +34,14 @@ module Mumukit::Auth
35
34
  raise Mumukit::Auth::InvalidTokenError.new(e)
36
35
  end
37
36
 
38
- def self.encode_dummy_auth_header(metadata)
39
- encoded_token = JWT.encode(
40
- {aud: Mumukit::Auth.config.client_id,
41
- app_metadata: metadata},
42
- decoded_secret)
43
- 'dummy token ' + encoded_token
37
+ def self.decode_header(header)
38
+ raise Mumukit::Auth::InvalidTokenError.new('missing authorization header') if header.nil?
39
+ decode header.split(' ').last
44
40
  end
45
41
 
46
42
  def self.decoded_secret
47
43
  JWT.base64url_decode(Mumukit::Auth.config.client_secret)
48
44
  end
49
45
  end
50
-
51
-
52
- class Permissions
53
- def to_mumukit_auth_permissions
54
- self
55
- end
56
- end
57
46
  end
58
47
 
59
- class String
60
- def to_mumukit_auth_permissions
61
- Mumukit::Auth::Permissions.parse(self)
62
- end
63
- end
64
-
65
- class NilClass
66
- def to_mumukit_auth_permissions
67
- Mumukit::Auth::Permissions.new([])
68
- end
69
- end
@@ -1,5 +1,5 @@
1
1
  module Mumukit
2
2
  module Auth
3
- VERSION = '1.0.2'
3
+ VERSION = '2.0.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mumukit-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Franco Leonardo Bulgarelli
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-12 00:00:00.000000000 Z
11
+ date: 2016-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: auth0
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: mumukit-core
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -102,12 +88,14 @@ extensions: []
102
88
  extra_rdoc_files: []
103
89
  files:
104
90
  - lib/mumukit/auth.rb
91
+ - lib/mumukit/auth/array.rb
105
92
  - lib/mumukit/auth/exceptions.rb
106
93
  - lib/mumukit/auth/grant.rb
107
- - lib/mumukit/auth/metadata.rb
108
94
  - lib/mumukit/auth/permissions.rb
95
+ - lib/mumukit/auth/roles.rb
96
+ - lib/mumukit/auth/scope.rb
97
+ - lib/mumukit/auth/slug.rb
109
98
  - lib/mumukit/auth/token.rb
110
- - lib/mumukit/auth/user.rb
111
99
  - lib/mumukit/auth/version.rb
112
100
  homepage: http://github.com/mumuki/mumukit-auth
113
101
  licenses:
@@ -1,70 +0,0 @@
1
- class Mumukit::Auth::Metadata
2
- def initialize(json)
3
- @json = json
4
- end
5
-
6
- def as_json(_options={})
7
- @json
8
- end
9
-
10
- def permissions(app)
11
- @json.dig(app, 'permissions').to_mumukit_auth_permissions
12
- end
13
-
14
- def add_permission!(app, permission)
15
- if permissions(app).present?
16
- @json[app] = process_add_permission(app, permission)
17
- else
18
- @json.merge!("#{app}" => {'permissions' => permission})
19
- end
20
- end
21
-
22
- def remove_permission!(app, permission)
23
- if permissions(app).present?
24
- @json[app] = process_remove_permission(app, permission)
25
- end
26
- @json.delete(app) if @json.dig(app, 'permissions').blank?
27
- end
28
-
29
- def process_permission(new_permissions)
30
- {'permissions' => Mumukit::Auth::Permissions.load(new_permissions).to_s}
31
- end
32
-
33
- def process_remove_permission(app, permission)
34
- process_permission(permissions(app).as_json.split(':').reject { |it| it == permission }.join(':'))
35
- end
36
-
37
- def process_add_permission(app, permission)
38
- process_permission(permissions(app).as_json + ":#{permission}")
39
- end
40
-
41
- def librarian?(slug)
42
- has_role? 'bibliotheca', slug
43
- end
44
-
45
- def admin?(slug)
46
- has_role? 'admin', slug
47
- end
48
-
49
- def teacher?(slug)
50
- has_role? 'classroom', slug
51
- end
52
-
53
- def student?(slug)
54
- has_role? 'atheneum', slug
55
- end
56
-
57
- def self.load(json)
58
- new(JSON.parse(json))
59
- end
60
-
61
- def self.dump(metadata)
62
- metadata.to_json
63
- end
64
-
65
- private
66
-
67
- def has_role?(app, slug)
68
- permissions(app)[slug]
69
- end
70
- end
@@ -1,78 +0,0 @@
1
- require 'auth0'
2
-
3
- class Mumukit::Auth::User
4
-
5
- attr_accessor :social_id, :user
6
-
7
- def initialize(social_id, user=nil)
8
- @social_id = social_id
9
- @user = user || client.user(@social_id)
10
- end
11
-
12
- def add_permission!(key, permission)
13
- metadata.add_permission!(key, permission)
14
- update_user_metadata!
15
- end
16
-
17
- def remove_permission!(key, permission)
18
- metadata.remove_permission!(key, permission)
19
- update_user_metadata!
20
- end
21
-
22
- def permissions_string
23
- apps.select { |app| @user[app].present? }.map { |app| {app.to_s => @user[app]} }.reduce({}, :merge).to_json
24
- end
25
-
26
- def metadata
27
- @metadata ||= Mumukit::Auth::Metadata.load(permissions_string)
28
- end
29
-
30
- def permissions_for(app)
31
- metadata[app]['permissions']
32
- end
33
-
34
- def apps
35
- ['bibliotheca', 'classroom', 'admin', 'atheneum']
36
- end
37
-
38
- def client
39
- self.class.client
40
- end
41
-
42
- def librarian?(slug)
43
- metadata.librarian? slug
44
- end
45
-
46
- def admin?(slug)
47
- metadata.admin? slug
48
- end
49
-
50
- def teacher?(slug)
51
- metadata.teacher? slug
52
- end
53
-
54
- def student?(slug)
55
- metadata.student? slug
56
- end
57
-
58
- def self.from_email(email)
59
- user = client.users("email:#{email}").first
60
- raise Mumukit::Auth::EmailNotRegistered.new('There is no user registered with that email.') unless user.present?
61
- new(user['user_id'])
62
- end
63
-
64
- def self.client
65
- Auth0Client.new(
66
- :client_id => ENV['MUMUKI_AUTH0_CLIENT_ID'],
67
- :client_secret => ENV['MUMUKI_AUTH0_CLIENT_SECRET'],
68
- :domain => "mumuki.auth0.com"
69
- )
70
- end
71
-
72
- private
73
-
74
- def update_user_metadata!
75
- client.update_user_metadata social_id, metadata.as_json
76
- end
77
-
78
- end