class Sgfa::Binder
The basic administrative unit in the Sgfa
system. A binder provides access control for a collection of {Jacket}s. In addition it allows values to be set for the Binder
itself, to use in managing a collection of Binders.
Constants
- LimJacketInv
Invalid chars in jacket name
- LimJacketMax
Maximum characters in jacket name
- LimPermInv
Invalid chars in permission
- LimPermMax
Max chars in permission
- LimSettingInv
Invalid chars in value setting
- LimSettingMax
Max chars in value setting
- LimTitleInv
Invalid chars in jacket title
- LimTitleMax
Max characters in jacket title
- LimValueInv
Invalid chars in value name
- LimValueMax
Max chars in value name
Public Class Methods
Limits check, create values
# File lib/sgfa/binder.rb, line 117 def self.limits_create(info) if !info.is_a?(Hash) raise Error::Limits, 'Binder create info is not a hash' end if !info[:jackets].is_a?(Array) raise Error::Limits, 'Binder create info :jackets is not an array' end info[:jackets].each do |jin| if !jin.is_a?(Hash) raise Error::Limits, 'Binder create info jacket not a hash' end Binder.limits_jacket(jin[:name]) Binder.limits_title(jin[:title]) Binder.limits_perms(jin[:perms]) end if !info[:users].is_a?(Array) raise Error::Limits, 'Binder create info :users is not an array' end info[:users].each do |uin| if !uin.is_a?(Hash) raise Error::Limits, 'Binder create info user not a hash' end History.limits_user(uin[:name]) Binder.limits_perms(uin[:perms]) end if !info[:values].is_a?(Array) raise Error::Limits, 'Binder create info :values is not an array' end info[:values].each do |ary| if !ary.is_a?(Array) raise Error::Limits, 'Binder create info :value items are not arrays' end vn, vs = ary Binder.limits_value(vn) Binder.limits_setting(vs) end Jacket.limits_id(info[:id_text]) if info[:id_text] end
Limits checks, jacket name
# File lib/sgfa/binder.rb, line 43 def self.limits_jacket(str) Error.limits(str, 1, LimJacketMax, LimJacketInv, 'Jacket name') end
Limits checks, permission array
# File lib/sgfa/binder.rb, line 73 def self.limits_perms(ary) if !ary.is_a?(Array) raise Error::Limits, 'Permission array required' end ary.each do |prm| Error.limits(prm, 1, LimPermMax, LimPermInv, 'Permission') end end
Limits checks, value setting
# File lib/sgfa/binder.rb, line 110 def self.limits_setting(str) Error.limits(str, 1, LimSettingMax, LimSettingInv, 'Value setting') end
Limits checks, jacket title
# File lib/sgfa/binder.rb, line 58 def self.limits_title(str) Error.limits(str, 1, LimTitleMax, LimTitleInv, 'Jacket title') end
Limits checks, value name
# File lib/sgfa/binder.rb, line 93 def self.limits_value(str) stc = str.is_a?(Symbol) ? str.to_s : str Error.limits(stc, 1, LimValueMax, LimValueInv, 'Value name') end
Public Instance Methods
Pull from backup store
@param bsto [Store] Backup store @param opts [Hash] Options @option opts [Hash] :log The log. Defaults to SDTERR at warn level
# File lib/sgfa/binder.rb, line 476 def backup_pull(bsto, opts={}) log = opts[:log] if !log log = Logger.new(STDERR) log.level = Logger::WARN end @lock.do_ex do # control jacket ctl = _jacket_open(0) begin log.info('Backup pull control %s' % @id_hash) ctl.restore(bsto, log: log) log.info('Backup pull update cache state') _update(ctl) ensure ctl.close() end _cache_write() # all other jackets @jackets.values.each do |info| begin jck = _jacket_open(info[:num]) rescue Error::NonExistent log.info('Backup pull create jacket %s' % info[:id_hash]) _jacket_create_raw(info) jck = _jacket_open(info[:num]) end begin log.info('Backup pull jacket %s' % info[:id_hash]) jck.restore(bsto, log: log) ensure jck.close end end end end
Push to a backup store
@param bsto [Store] Backup store @param opts [Hash] Options @option opts [Hash] :log The log. Defaults to STDERR at warn level. @option opts [Hash] :prev Jacket
id_hash to previously pushed max history @return [Hash] Jacket
id_hash to max history backed up
# File lib/sgfa/binder.rb, line 429 def backup_push(bsto, opts={}) stat = {} prev = opts[:prev] || {} log = opts[:log] if !log log = Logger.new(STDERR) log.level = Logger::WARN end # control jacket push jcks = nil _shared do ctl = _jacket_open(0) begin min = (prev[@id_hash] || 0) + 1 log.info('Backup push control %s at %d' % [@id_hash, min]) stat[@id_hash] = ctl.backup(bsto, min_history: min, log: log) ensure ctl.close end jcks = @jackets.values end # all other jackets jcks.each do |info| jck = _jacket_open(info[:num]) begin id = info[:id_hash] min = (prev[id] || 0) + 1 log.info('Backup push jacket %s at %d' % [id, min]) stat[id] = jck.backup(bsto, min_history: min, log: log) ensure jck.close end end return stat end
Get info
@param tr (see jacket_create
) @return [Hash] Containing :id_hash, :id_text, :jackets, :values, :users
# File lib/sgfa/binder.rb, line 254 def binder_info(tr) _shared do _perms(tr, ['info']) { :id_hash => @id_hash.dup, :id_text => @id_text.dup, :values => @values, :jackets => @jackets, :users => @users, } end end
Set user or group permissions
@param tr (see jacket_create
) @param perms [Array] New user/group permissions
# File lib/sgfa/binder.rb, line 220 def binder_user(tr, user, perms) History.limits_user(user) Binder.limits_perms(perms) _control(tr) do |jck| ent = _control_user(tr, user, perms) jck.write(tr[:user], [ent]) @users end end
Set values
@param tr (see jacket_create
) @param vals [Hash] New values
# File lib/sgfa/binder.rb, line 236 def binder_values(tr, vals) vals.each do |vn, vs| Binder.limits_value(vn) Binder.limits_setting(vs) end _control(tr) do |jck| ent = _control_values(tr, vals) jck.write(tr[:user], [ent]) @values end end
Create a jacket
@param tr [Hash] Common transaction info @option tr [String] :jacket Jacket
name @option tr [String] :user User name @option tr [Array] :groups List of groups to which :user belongs @option tr [String] :title Title of the entry @option tr [String] :body Body of the entry @param title [String] Title of the jacket @param perms [Array] Permissions for the jacket
# File lib/sgfa/binder.rb, line 176 def jacket_create(tr, title, perms) Binder.limits_title(title) Binder.limits_perms(perms) _control(tr) do |jck| _perms(tr, ['manage']) num = @jackets.size + 1 id_text, id_hash = _jacket_create(num) ent = _control_jacket(tr, num, tr[:jacket], id_hash, id_text, title, perms) jck.write(tr[:user], [ent]) @jackets end end
Edit a jacket
@param tr (see jacket_create
) @param name [String] New jacket name @param title [String] New jacket title @param perms [Array] New jacket permissions
# File lib/sgfa/binder.rb, line 198 def jacket_edit(tr, name, title, perms) Binder.limits_jacket(name) Binder.limits_title(title) Binder.limits_perms(perms) _control(tr) do |jck| jnam = tr[:jacket] raise Error::NonExistent, 'Jacket does not exist' if !@jackets[jnam] jacket = @jackets[jnam] num = jacket['num'] ent = _control_jacket(tr, num, name, jacket['id_hash'], jacket['id_text'], title, perms) jck.write(tr[:user], [ent]) @jackets end end
Read an attachment
@param tr (see jacket_create
) @param enum [Integer] Entry
number @param anum [Integer] Attachment number @param hnum [Integer] History
number @return [File] Attachment
# File lib/sgfa/binder.rb, line 381 def read_attach(tr, enum, anum, hnum) _jacket(tr, 'read') do |jck| cur = jck.read_entry(enum, 0) pl = cur.perms _perms(tr, pl) if !pl.empty? jck.read_attach(enum, anum, hnum) end end
Read an entry
@param tr (see jacket_create
) @param enum [Integer] Entry
number @param rnum [Integer] Revision number @return [Entry] the Requested entry
# File lib/sgfa/binder.rb, line 348 def read_entry(tr, enum, rnum=0) _jacket(tr, 'read') do |jck| cur = jck.read_entry(enum, 0) pl = cur.perms _perms(tr, pl) if !pl.empty? if rnum == 0 cur else jck.read_entry(enum, rnum) end end end
Read a history item
@param tr (see jacket_create
) @param hnum [Integer] History
number @return [History] History
item requested
# File lib/sgfa/binder.rb, line 368 def read_history(tr, hnum) _jacket(tr, 'info'){|jck| jck.read_history(hnum) } end
Read list of tags
@param tr (see jacket_create
)
# File lib/sgfa/binder.rb, line 276 def read_list(tr) _jacket(tr, 'info'){|jck| jck.read_list } end
Read history log
@param tr (see jacket_create
) @param offs [Integer] Offset to begin reading @param max [Integer] Maximum number of histories to read
# File lib/sgfa/binder.rb, line 322 def read_log(tr, offs, max) _jacket(tr, 'info') do |jck| cur = jck.read_history() hmax = cur ? cur.history : 0 start = (offs <= hmax) ? hmax - offs : 0 stop = (start - max > 0) ? (start - (max-1)) : 1 ary = [] if start != 0 start.downto(stop) do |hnum| hst = jck.read_history(hnum) ary.push [hst.history, hst.time, hst.user, hst.entries.size, hst.attachments.size] end end [hmax, ary] end end
Read a tag
@param tr (see jacket_create
) @param tag [String] Tag name @param offs [Integer] Offset to begin reading @param max [Integer] Maximum number of entries to read @param opts [Hash] Options hash @option opts [Boolean] :raw Get the raw entry when permissions allow @return [Array] [ enum, rnum, hnum, time, title, num_tags, num_attach,
\] Or, if opts[:raw], each Array item will consist of the Entry or, if user does not have read permission, the info array.
# File lib/sgfa/binder.rb, line 293 def read_tag(tr, tag, offs, max, opts={}) raw = opts[:raw] _jacket(tr, 'read') do |jck| size, ents = jck.read_tag(tag, offs, max) lst = ents.map do |ent| if raw pl = ent.perms begin _perms(tr, pl) item = ent rescue Error::Permission item = nil end end item ||= [ent.entry, ent.revision, ent.history, ent.time, ent.title, ent.tags.size, ent.attachments.size] item end [size, lst] end end
Write entries
@param tr (see jacket_create
) @param ents [Array] List of entries to write @return (see Jacket#write
) @raise [Error::Permission] if user lacks require permissions @raise [Error::Conflict] if entry revision is not one up from current
# File lib/sgfa/binder.rb, line 399 def write(tr, ents) olde = ents.select{|ent| ent.entry } enums = olde.map{|ent| ent.entry } _jacket(tr, 'write') do |jck| cur = jck.read_array(enums) pl = [] cur.each{|ent| pl.concat ent.perms } _perms(tr, pl) if !pl.empty? enums.each_index do |idx| if cur[idx].revision + 1 != olde[idx].revision raise Error::Conflict, 'Entry revision conflict' end end jck.write(tr[:user], ents) end end
Private Instance Methods
Clear cache
# File lib/sgfa/binder.rb, line 821 def _cache_clear @jackets = nil @users = nil @values = nil end
Edit control jacket
@param tr (see jacket_create
)
# File lib/sgfa/binder.rb, line 714 def _control(tr) ret = nil @lock.do_ex do _cache_read() _perms(tr, ['manage']) begin ctl = _jacket_open(0) begin ret = yield(ctl) ensure ctl.close() end _cache_write() ensure _cache_clear() end end # @lock_do return ret end
Set jacket info
# File lib/sgfa/binder.rb, line 753 def _control_jacket(tr, num, name, id_hash, id_text, title, perms) info = { num: num, name: name, id_hash: id_hash, id_text: id_text, title: title, perms: perms, } json = JSON.pretty_generate(info) ent = Entry.new ent.tag( 'jacket: %d' % num ) ent.title = tr[:title] ent.body = tr[:body] + "\n" + json + "\n" @jackets.delete(tr[:jacket]) @jackets[name] = info return ent end
Set user permissions in the control jacket
# File lib/sgfa/binder.rb, line 778 def _control_user(tr, user, perms) info = { name: user, perms: perms, } json = JSON.pretty_generate(info) ent = Entry.new ent.tag( 'user: %s' % user ) ent.title = tr[:title] ent.body = tr[:body] + "\n" + json + "\n" @users[user.dup] = perms.map{|pr| pr.dup } return ent end
Set binder values in the control jacket
# File lib/sgfa/binder.rb, line 798 def _control_values(tr, vals) json = JSON.pretty_generate(vals) ent = Entry.new ent.tag( 'values' ) ent.title = tr[:title] ent.body = tr[:body] + "\n" + json + "\n" vals.each do |val, sta| vas = val.is_a?(Symbol) ? val : val.to_s if sta @values[val] = sta else @values.delete(val) end end return ent end
Shared creation stuff
@param ctl [Jacket] Control jacket @param tr (see jacket_create
) @param info [Hash] New binder creation options @option info [Array] :jackets List of jackets [name, title, perms] @option info [Array] :users List of users [name, perms] @option info [Hash] :values List of values name => setting
# File lib/sgfa/binder.rb, line 615 def _create(ctl, tr, info) # check all the values are okay History.limits_user(tr[:user]) Entry.limits_title(tr[:title]) Entry.limits_body(tr[:body]) Binder.limits_create(info) ents = [] # jackets num = 0 info[:jackets].each do |jin| num += 1 trj = { :title => 'Create binder initial jacket \'%s\'' % jin[:name], :jacket => jin[:name], :body => "Create binder initial jacket\n\n", } id_text, id_hash = _jacket_create(num) ents.push _control_jacket(trj, num, jin[:name], id_hash, id_text, jin[:title], jin[:perms]) end # users info[:users].each do |uin| tru = { :title => 'Create binder initial user \'%s\'' % uin[:name], :body => "Create binder initial user\n\n", } ents.push _control_user(tr, uin[:name], uin[:perms]) end # values ents.push _control_values(tr, info[:values]) ctl.write(tr[:user], ents) end
Get json from a body
# File lib/sgfa/binder.rb, line 587 def _get_json(ent) lines = ent.body.lines st = lines.index{|li| li[0] == '{' || li[0] == '[' } if !st || (lines[-1][0] != '}' && lines[-1][0] != ']') puts ent.body.inspect raise Error::Corrupt, 'Control jacket entry does not contain JSON' end json = lines[st..-1].join info = nil begin info = JSON.parse(json) rescue raise Error::Corrupt, 'Control jacket entry JSON parse error' end return info end
Access a jacket
@param tr (see jacket_create
) @param perm [String] Basic permission needed (write, read, info)
# File lib/sgfa/binder.rb, line 692 def _jacket(tr, perm) ret = nil _shared do jnam = tr[:jacket] raise Error::NonExistent, 'Jacket does not exist' if !@jackets[jnam] pl = [perm].concat @jackets[jnam][:perms] _perms(tr, pl) jck = _jacket_open(@jackets[jnam][:num]) begin ret = yield(jck) ensure jck.close end end return ret end
Permission check
@param tr (see jacket_create
) @param plst [Array] Permissions required @raise [Error::Permissions] if require permissions not met
# File lib/sgfa/binder.rb, line 660 def _perms(tr, plst) if tr[:perms] usr_has = tr[:perms] else usr = tr[:user] grp = tr[:groups] usr_has = [] usr_has.concat(@users[usr]) if @users[usr] grp.each{|gr| usr_has.concat(@users[gr]) if @users[gr] } if usr_has.include?('write') usr_has.concat ['read', 'info'] elsif usr_has.include?('read') || usr_has.include?('manage') usr_has.push 'info' end usr_has.uniq! tr[:perms] = usr_has end miss = [] plst.each{|pr| miss.push(pr) if !usr_has.include?(pr) } if !miss.empty? raise Error::Permission, 'User lacks permission(s): ' + miss.join(', ') end end
Update cache
# File lib/sgfa/binder.rb, line 525 def _update(ctl) values = {} users = {} jackets = {} ctl.read_list.each do |tag| # values if tag == 'values' # process all values entries offs = 0 while true size, ary = ctl.read_tag(tag, offs, 2) break if ary.empty? ary.each do |ent| info = _get_json(ent) info.each do |val, sta| next if values.has_key?(val) values[val] = sta end end offs += 2 end # clear unset values values.delete_if{ |val, sta| !sta.is_a?(String) } # jacket elsif /^jacket:/.match(tag) size, ary = ctl.read_tag(tag, 0, 1) info = _get_json(ary.first) jackets[info['name']] = { num: info['num'], name: info['name'], id_hash: info['id_hash'], id_text: info['id_text'], title: info['title'], perms: info['perms'], } # user elsif /^user:/.match(tag) size, ary = ctl.read_tag(tag, 0, 1) info = _get_json(ary.first) name = info['name'] perms = info['perms'] users[name] = perms end end @users = users @values = values @jackets = jackets end