yus 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.
- data/Guide.txt +183 -0
- data/History.txt +6 -0
- data/InstalledFiles +23 -0
- data/LICENSE +339 -0
- data/Manifest.txt +29 -0
- data/README.txt +39 -0
- data/Rakefile +28 -0
- data/bin/yus_add_user +72 -0
- data/bin/yus_delete_user +58 -0
- data/bin/yus_grant +80 -0
- data/bin/yus_passwd +68 -0
- data/bin/yus_show +65 -0
- data/bin/yusd +99 -0
- data/config.save +12 -0
- data/data/yus.crt +14 -0
- data/data/yus.key +15 -0
- data/install.rb +1098 -0
- data/lib/yus/entity.rb +140 -0
- data/lib/yus/persistence/odba.rb +77 -0
- data/lib/yus/persistence/og.rb +37 -0
- data/lib/yus/privilege.rb +63 -0
- data/lib/yus/server.rb +116 -0
- data/lib/yus/session.rb +335 -0
- data/sha256.rb +3 -0
- data/test/suite.rb +8 -0
- data/test/test_entity.rb +241 -0
- data/test/test_privilege.rb +56 -0
- data/test/test_server.rb +132 -0
- data/test/test_session.rb +836 -0
- metadata +125 -0
data/lib/yus/entity.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Entity -- yus -- 29.05.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'yus/privilege'
|
5
|
+
|
6
|
+
module Yus
|
7
|
+
class YusError < RuntimeError; end
|
8
|
+
class AuthenticationError < YusError; end
|
9
|
+
class CircularAffiliationError < YusError; end
|
10
|
+
class DuplicateNameError < YusError; end
|
11
|
+
class NotPrivilegedError < YusError; end
|
12
|
+
class UnknownEntityError < YusError; end
|
13
|
+
class Entity
|
14
|
+
attr_reader :name, :affiliations, :privileges
|
15
|
+
attr_accessor :valid_from, :valid_until
|
16
|
+
attr_writer :passhash
|
17
|
+
def initialize(name, valid_until=nil, valid_from=Time.now)
|
18
|
+
@name = name.to_s
|
19
|
+
@valid_until = valid_until
|
20
|
+
@valid_from = valid_from
|
21
|
+
@affiliations = []
|
22
|
+
@privileges = Hash.new(false)
|
23
|
+
@preferences = {}
|
24
|
+
@last_logins = {}
|
25
|
+
@tokens = {}
|
26
|
+
end
|
27
|
+
def allowed?(action, item=:everything)
|
28
|
+
valid? && privileged?(action, item) \
|
29
|
+
|| @affiliations.any? { |entity| entity.allowed?(action, item) }
|
30
|
+
end
|
31
|
+
def authenticate(passhash)
|
32
|
+
@passhash == passhash.to_s
|
33
|
+
end
|
34
|
+
def authenticate_token(token)
|
35
|
+
if expires = (@tokens ||= {}).delete(token)
|
36
|
+
expires > Time.now
|
37
|
+
else
|
38
|
+
# Hacking-Attempt? Remove all Tokens for this user.
|
39
|
+
@tokens.clear
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def detect_circular_affiliation(entity)
|
44
|
+
_detect_circular_affiliation(entity)
|
45
|
+
rescue CircularAffiliationError => error
|
46
|
+
error.message << ' <- "' << entity.name << '"'
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
def grant(action, item=:everything, expires=:never)
|
50
|
+
action = Entity.sanitize(action)
|
51
|
+
(@privileges[action] ||= Privilege.new).grant(item, expires)
|
52
|
+
end
|
53
|
+
def info(recursive=false)
|
54
|
+
info = [ @name ]
|
55
|
+
@privileges.sort.each { |action, priv|
|
56
|
+
info.push [action, priv.info]
|
57
|
+
}
|
58
|
+
if recursive
|
59
|
+
@affiliations.sort_by { |entity| entity.name }.each { |entity|
|
60
|
+
info.push entity.info(true)
|
61
|
+
}
|
62
|
+
elsif !@affiliations.empty?
|
63
|
+
info.push @affiliations.collect { |entity| entity.name }.sort
|
64
|
+
end
|
65
|
+
info
|
66
|
+
end
|
67
|
+
def join(party)
|
68
|
+
unless(@affiliations.include?(party))
|
69
|
+
party.detect_circular_affiliation(self)
|
70
|
+
@affiliations.push(party)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
def last_login(domain)
|
74
|
+
(@last_logins ||= {})[domain]
|
75
|
+
end
|
76
|
+
def leave(party)
|
77
|
+
@affiliations.delete(party)
|
78
|
+
end
|
79
|
+
def login(domain)
|
80
|
+
(@last_logins ||= {}).store(domain, Time.now)
|
81
|
+
end
|
82
|
+
def get_preference(key, domain='global')
|
83
|
+
domain_preferences(domain)[key] || domain_preferences('global')[key]
|
84
|
+
end
|
85
|
+
def privileged?(action, item=:everything)
|
86
|
+
(privilege = @privileges[Entity.sanitize(action)]) \
|
87
|
+
&& privilege.granted?(item)
|
88
|
+
end
|
89
|
+
def privileged_until(action, item=:everything)
|
90
|
+
if(privilege = @privileges[Entity.sanitize(action)])
|
91
|
+
privilege.expiry_time(item)
|
92
|
+
else
|
93
|
+
raise NotPrivilegedError
|
94
|
+
end
|
95
|
+
end
|
96
|
+
def remove_token(token)
|
97
|
+
@tokens.delete(token) if @tokens
|
98
|
+
end
|
99
|
+
def rename(new_name)
|
100
|
+
@name = new_name
|
101
|
+
end
|
102
|
+
def revoke(action, item=:everything, time=nil)
|
103
|
+
action = Entity.sanitize(action)
|
104
|
+
if(priv = @privileges[action])
|
105
|
+
priv.revoke(item, time)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
def set_preference(key, value, domain=nil)
|
109
|
+
domain_preferences(domain || 'global')[key] = value
|
110
|
+
end
|
111
|
+
def set_token(token, expires)
|
112
|
+
(@tokens ||= {}).store token, expires
|
113
|
+
end
|
114
|
+
def to_s
|
115
|
+
@name
|
116
|
+
end
|
117
|
+
def valid?
|
118
|
+
now = Time.now
|
119
|
+
@valid_from < now && (!@valid_until || @valid_until > now)
|
120
|
+
end
|
121
|
+
def Entity.sanitize(term)
|
122
|
+
term.to_s.downcase
|
123
|
+
end
|
124
|
+
protected
|
125
|
+
def _detect_circular_affiliation(entity)
|
126
|
+
if(@affiliations.include?(entity))
|
127
|
+
raise CircularAffiliationError,
|
128
|
+
"circular affiliation detected: \"#{entity.name}\""
|
129
|
+
end
|
130
|
+
@affiliations.each { |aff| aff._detect_circular_affiliation(entity) }
|
131
|
+
rescue CircularAffiliationError => error
|
132
|
+
error.message << ' <- "' << @name << '"'
|
133
|
+
raise
|
134
|
+
end
|
135
|
+
private
|
136
|
+
def domain_preferences(domain)
|
137
|
+
@preferences[domain] ||= {}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Persistence::ODBA -- yus -- 31.05.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'odba'
|
5
|
+
require 'odba/drbwrapper'
|
6
|
+
require 'yus/entity'
|
7
|
+
require 'yus/server'
|
8
|
+
require 'yus/session'
|
9
|
+
|
10
|
+
module Yus
|
11
|
+
module Persistence
|
12
|
+
class Odba
|
13
|
+
def initialize
|
14
|
+
@entities = ODBA.cache.fetch_named('entities', self) { Hash.new }
|
15
|
+
end
|
16
|
+
def add_entity(entity)
|
17
|
+
@entities.store(Entity.sanitize(entity.name), entity)
|
18
|
+
entity.odba_store
|
19
|
+
@entities.odba_store
|
20
|
+
entity
|
21
|
+
end
|
22
|
+
def delete_entity(name)
|
23
|
+
if entity = @entities.delete(Entity.sanitize(name))
|
24
|
+
@entities.odba_store
|
25
|
+
affiliations = entity.affiliations
|
26
|
+
affiliations.odba_delete unless affiliations.odba_unsaved?
|
27
|
+
entity.odba_delete
|
28
|
+
entity
|
29
|
+
end
|
30
|
+
end
|
31
|
+
def entities
|
32
|
+
@entities.values
|
33
|
+
end
|
34
|
+
def find_entity(name)
|
35
|
+
@entities[Entity.sanitize(name)]
|
36
|
+
end
|
37
|
+
def save_entity(entity)
|
38
|
+
if(@entities[entity.name])
|
39
|
+
entity.odba_store
|
40
|
+
else
|
41
|
+
@entities.delete_if { |name, ent| ent.name == entity.name }
|
42
|
+
add_entity(entity)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
class Entity
|
48
|
+
include ODBA::Persistable
|
49
|
+
ODBA_SERIALIZABLE = ['@last_logins', '@privileges', '@preferences', '@tokens']
|
50
|
+
alias :odba_join :join
|
51
|
+
alias :odba_leave :leave
|
52
|
+
def join(entity)
|
53
|
+
res = odba_join(entity)
|
54
|
+
@affiliations.odba_store
|
55
|
+
res
|
56
|
+
end
|
57
|
+
def leave(entity)
|
58
|
+
res = odba_leave(entity)
|
59
|
+
@affiliations.odba_store
|
60
|
+
res
|
61
|
+
end
|
62
|
+
end
|
63
|
+
class Server
|
64
|
+
alias :odba_login_entity :login_entity
|
65
|
+
alias :odba_login_root :login_root
|
66
|
+
def login_entity(*args)
|
67
|
+
if(entity = odba_login_entity(*args))
|
68
|
+
ODBA::DRbWrapper.new(entity)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
def login_root(*args)
|
72
|
+
if(root = odba_login_root(*args))
|
73
|
+
ODBA::DRbWrapper.new(root)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Persistence::Og -- yus -- 31.05.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'og'
|
5
|
+
require 'yus/entity'
|
6
|
+
require 'yus/server'
|
7
|
+
|
8
|
+
module Yus
|
9
|
+
module Persistence
|
10
|
+
class Og
|
11
|
+
def add_entity(entity)
|
12
|
+
entity.save
|
13
|
+
end
|
14
|
+
def entities
|
15
|
+
Entity.find_all
|
16
|
+
end
|
17
|
+
def find_entity(name)
|
18
|
+
Entity.find_by_name(name)
|
19
|
+
end
|
20
|
+
def save_entity(entity)
|
21
|
+
entity.save
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
class Privilege
|
26
|
+
property :expiry_time, Time
|
27
|
+
property :items, Hash
|
28
|
+
end
|
29
|
+
class Entity
|
30
|
+
property :name, String
|
31
|
+
property :valid_from, Time
|
32
|
+
property :valid_until, Time
|
33
|
+
property :preferences, Hash
|
34
|
+
has_many :affiliations, Entity
|
35
|
+
has_many :privileges, Privilege
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Privilege -- yus -- 31.05.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
module Yus
|
5
|
+
class Privilege
|
6
|
+
attr_writer :expiry_time
|
7
|
+
def initialize
|
8
|
+
@items = {}
|
9
|
+
end
|
10
|
+
def expiry_time(item=:everything)
|
11
|
+
if(time = [@items[item], @items[:everything]].compact.max)
|
12
|
+
time if time.is_a?(Time)
|
13
|
+
else
|
14
|
+
raise NotPrivilegedError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
def grant(item, expiry_time=:never)
|
18
|
+
@items.store(item, expiry_time)
|
19
|
+
end
|
20
|
+
def granted?(item)
|
21
|
+
if(expiry_time = @items[item])
|
22
|
+
case expiry_time
|
23
|
+
when Time
|
24
|
+
Time.now < expiry_time
|
25
|
+
else
|
26
|
+
true
|
27
|
+
end
|
28
|
+
elsif(@items.include?(:everything))
|
29
|
+
# check time
|
30
|
+
granted?(:everything)
|
31
|
+
else
|
32
|
+
item = item.to_s.dup
|
33
|
+
if(item[-1] != ?*)
|
34
|
+
while(!item.empty?)
|
35
|
+
item.slice!(/[^.]*$/)
|
36
|
+
if(granted?(item + "*"))
|
37
|
+
return true
|
38
|
+
end
|
39
|
+
item.chop!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
def info
|
46
|
+
@items.collect { |item, time|
|
47
|
+
info = [item.to_s]
|
48
|
+
if time.is_a?(Time)
|
49
|
+
info.push time
|
50
|
+
end
|
51
|
+
info
|
52
|
+
}.sort
|
53
|
+
end
|
54
|
+
def revoke(item, expiry_time=nil)
|
55
|
+
case expiry_time
|
56
|
+
when Time
|
57
|
+
@items.store(item, expiry_time)
|
58
|
+
else
|
59
|
+
@items.delete(item)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/yus/server.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Server -- yus -- 31.05.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'drb'
|
5
|
+
require 'yus/entity'
|
6
|
+
require 'yus/session'
|
7
|
+
require 'needle'
|
8
|
+
|
9
|
+
VERSION = '1.0.0'
|
10
|
+
|
11
|
+
module Yus
|
12
|
+
class Server
|
13
|
+
def initialize(persistence, config, logger)
|
14
|
+
@needle = Needle::Registry.new
|
15
|
+
@needle.register(:persistence) { persistence }
|
16
|
+
@needle.register(:config) { config }
|
17
|
+
@needle.register(:logger) { logger }
|
18
|
+
@sessions = []
|
19
|
+
run_cleaner
|
20
|
+
end
|
21
|
+
def autosession(domain, &block)
|
22
|
+
session = AutoSession.new(@needle, domain)
|
23
|
+
block.call(session)
|
24
|
+
end
|
25
|
+
def login(name, password, domain)
|
26
|
+
@needle.logger.info(self.class) {
|
27
|
+
sprintf('Login attempt for %s from %s', name, domain)
|
28
|
+
}
|
29
|
+
hash = @needle.config.digest.hexdigest(password.to_s)
|
30
|
+
session = login_root(name, hash, domain) \
|
31
|
+
|| login_entity(name, hash, domain) # raises YusError
|
32
|
+
@sessions.push(session)
|
33
|
+
session
|
34
|
+
end
|
35
|
+
def login_token(name, token, domain)
|
36
|
+
entity = authenticate_token(name, token)
|
37
|
+
entity.login(domain)
|
38
|
+
@needle.persistence.save_entity(entity)
|
39
|
+
timeout = entity.get_preference("session_timeout", domain) \
|
40
|
+
|| @needle.config.session_timeout
|
41
|
+
TokenSession.new(@needle, entity, domain)
|
42
|
+
end
|
43
|
+
def logout(session)
|
44
|
+
@needle.logger.info(self.class) {
|
45
|
+
sprintf('Logout for %s', session)
|
46
|
+
}
|
47
|
+
@sessions.delete(session)
|
48
|
+
if(session.respond_to?(:destroy!))
|
49
|
+
session.destroy!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
def ping
|
53
|
+
true
|
54
|
+
end
|
55
|
+
private
|
56
|
+
def authenticate(name, passhash)
|
57
|
+
user = @needle.persistence.find_entity(name) \
|
58
|
+
or raise UnknownEntityError, "Unknown Entity '#{name}'"
|
59
|
+
user.authenticate(passhash) \
|
60
|
+
or raise AuthenticationError, "Wrong password"
|
61
|
+
@needle.logger.info(self.class) {
|
62
|
+
sprintf('Authentication succeeded for %s', name)
|
63
|
+
}
|
64
|
+
user
|
65
|
+
rescue YusError
|
66
|
+
@needle.logger.warn(self.class) {
|
67
|
+
sprintf('Authentication failed for %s', name)
|
68
|
+
}
|
69
|
+
raise
|
70
|
+
end
|
71
|
+
def authenticate_token(name, token)
|
72
|
+
user = @needle.persistence.find_entity(name) \
|
73
|
+
or raise UnknownEntityError, "Unknown Entity '#{name}'"
|
74
|
+
user.authenticate_token(token) \
|
75
|
+
or raise AuthenticationError, "Wrong token or token expired"
|
76
|
+
@needle.logger.info(self.class) {
|
77
|
+
sprintf('Token-Authentication succeeded for %s', name)
|
78
|
+
}
|
79
|
+
user
|
80
|
+
rescue YusError
|
81
|
+
@needle.persistence.save_entity(user) if user
|
82
|
+
@needle.logger.warn(self.class) {
|
83
|
+
sprintf('Token-Authentication failed for %s', name)
|
84
|
+
}
|
85
|
+
raise
|
86
|
+
end
|
87
|
+
def clean
|
88
|
+
@sessions.delete_if { |session| session.expired? }
|
89
|
+
end
|
90
|
+
def login_entity(name, passhash, domain)
|
91
|
+
entity = authenticate(name, passhash)
|
92
|
+
entity.login(domain)
|
93
|
+
@needle.persistence.save_entity(entity)
|
94
|
+
timeout = entity.get_preference("session_timeout", domain) \
|
95
|
+
|| @needle.config.session_timeout
|
96
|
+
EntitySession.new(@needle, entity, domain)
|
97
|
+
end
|
98
|
+
def login_root(name, passhash, domain)
|
99
|
+
if(name == @needle.config.root_name \
|
100
|
+
&& passhash == @needle.config.root_pass)
|
101
|
+
@needle.logger.info(self.class) {
|
102
|
+
sprintf('Authentication succeeded for root: %s', name)
|
103
|
+
}
|
104
|
+
RootSession.new(@needle)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
def run_cleaner
|
108
|
+
@cleaner = Thread.new {
|
109
|
+
loop {
|
110
|
+
sleep(@needle.config.cleaner_interval)
|
111
|
+
clean
|
112
|
+
}
|
113
|
+
}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|