mumukit-auth 1.0.2 → 2.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 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