class ICFS::Api
Constants
- ValActionSearch
Validate
a task search- ValActionTags
Task Tags search validation
- ValCaseSearch
Validate
a case search query- ValCaseTags
Case Tags search validation
- ValEntrySearch
Validate
an entry search query- ValEntryTags
Entry Tags search validation
- ValIndexSearch
Validate
an index search query- ValIndexTags
Validate
a index tag search- ValLogSearch
Validate
a log search- ValPage
Validate
a page- ValPurpose
Validate
a purpose- ValSize
Validate
a size- ValStatsSearch
validate the stats query
Attributes
Config
settings
Groups
Globals stats
Global perms
Roles
User, Roles, Groups set
User
Public Class Methods
New API
@param stats [Array<String>] Global stats @param users [Users] the User/role/group interface @param cache [Cache] the cache @param store [Store] the store @param config [Config] the config settings, optional
# File lib/icfs/api.rb, line 56 def initialize(stats, users, cache, store, config=nil) @users = users @cache = cache @store = store @gstats = stats.map{|st| st.dup.freeze }.freeze @config = config reset end
Public Instance Methods
Get an access list
@param cid [String] caseid @return [Set<String>] the perms granted the user for this case @raise [Error::NotFound] if case not found
# File lib/icfs/api.rb, line 199 def access_list(cid) if !@access.key?(cid) # get grants for the case cse = case_read(cid) al = Set.new cse['access'].each do |ac| gs = Set.new(ac['grant']) al.add(ac['perm']) if @urg.intersect?(gs) end # higher perms imply lower ones al.add(ICFS::PermRead) if al.include?(ICFS::PermManage) al.add(ICFS::PermWrite) if al.include?(ICFS::PermAction) al.add(ICFS::PermRead) if al.include?(ICFS::PermWrite) # merge in global perms al.merge @perms @access[cid] = al end return @access[cid] end
Read an action
@param cid [String] caseid @param anum [Integer] the action number @param lnum [Integer] the log number or 0 for current @return [Action] requested action @raise [Error::NotFound] if action is not found @raise [Error::Perms] if user does not have permissions
# File lib/icfs/api.rb, line 412 def action_read(cid, anum, lnum=0) # get current action ac = _action_read(cid, anum) # see if we can read the action unless _can_read?(cid, anum) raise(Error::Perms, 'missing perms: %s' % ICFS::PermRead ) end # return the requested version if( lnum == 0 || ac['log'] == lnum ) return ac else json = @store.action_read( cid, anum, lnum) return Items.parse(json, 'action', Items::ItemAction) end end
Search for actions
# File lib/icfs/api.rb, line 672 def action_search(query) Items.validate(query, 'Action Search', ValActionSearch) # permissions check # - have global search permissions / read access to the case # - searching for role you have unless( _search?(query) || @ur.include?(query[:assigned]) || (query[:assigned] == ICFS::UserCase && query[:caseid] && access_list(query[:caseid]).include?(ICFS::PermAction) )) raise(Error::Perms, 'Does not have permission to search') end # run the search return @cache.action_search(query) end
Create a new case
@param ent [Hash] the first entry @param cse [Hash] the case @param tid [String] the template name @param unam [String] the user name if not using the Users
API
# File lib/icfs/api.rb, line 903 def case_create(ent, cse, tid=nil, unam=nil) #################### # Sanity checks # form & values Items.validate(ent, 'entry', Items::ItemEntryNew) Items.validate(cse, 'case', Items::ItemCaseEdit) # get caseid cid = ent['caseid'] cid ||= cse['caseid'] unless cid raise(Error::Values, 'No caseid provided') end # access users/roles/groups are valid, unless manually specifying user unless unam cse["access"].each do |acc| acc["grant"].each do |gnt| urg = @users.read(gnt) if !urg raise(Error::NotFound, 'User/role/group %s not found' % urg) end end end end # permissions perms = Set[ ICFS::PermManage ] perms.merge(ent['perms']) if ent['perms'] # template if tid tmpl = case_read(tid) unless tmpl['template'] raise(Error::Perms, 'Not a template') end al = access_list(tid) unless al.include?(ICFS::PermManage) raise(Error::Perms, 'May not create cases from this template') end end # no action/indexes if ent['action'] raise(Error::Value, 'No Action for a new case entry') end if ent['index'] raise(Error::Value, 'No Index for a new case entry') end # Allow case creation without a Users system in place user = @user ? @user : unam raise(ArgumentError, 'No user specified') if user.nil? #################### # Prep # case cse['icfs'] = 1 cse['caseid'] = cid cse['log'] = 1 cse['tags'] ||= [ ICFS::TagNone ] cse['entry'] = 1 citem = Items.generate(cse, 'case', Items::ItemCase) # entry ent['icfs'] = 1 ent['entry'] = 1 ent['log'] = 1 ent['tags'] ||= [ ] ent['tags'] << ICFS::TagCase ent['user'] = user files, fhash = _pre_files(ent) # log log = { 'icfs' => 1, 'caseid' => cid, 'log' => 1, 'prev' => '0'*64, 'user' => user, 'entry' => { 'num' => 1, }, 'case' => { 'set' => true, 'hash' => ICFS.hash(citem), }, } log['files_hash'] = fhash if fhash # current cur = { 'icfs' => 1, 'caseid' => cid, 'log' => 1, 'entry' => 1, 'action' => 0, 'index' => 0 } #################### # Write the case # take lock @cache.lock_take(cid) begin if @cache.case_read(cid) raise(Error::Conflict, 'Case already exists') end now = Time.now.to_i # finish items ent['time'] ||= now ent['files'].each{|fi| fi['log'] ||= 1 } if ent['files'] eitem = Items.generate(ent, 'entry', Items::ItemEntry) log['time'] = now log['entry']['hash'] = ICFS.hash(eitem) litem = Items.generate(log, 'log', Items::ItemLog) cur['hash'] = ICFS.hash(litem) nitem = Items.generate(cur, 'current', Items::ItemCurrent) # write to cache @cache.entry_write(cid, 1, eitem) @cache.log_write(cid, 1, litem) @cache.case_write(cid, citem) @cache.current_write(cid, nitem) # write to store @store.entry_write(cid, 1, 1, eitem) @store.log_write(cid, 1, litem) @store.case_write(cid, 1, citem) # release lock ensure @cache.lock_release(cid) end # files files.each_index{|ix| @store.file_write(cid, 1, 1, ix+1, files[ix]) } end
Read a case
@param cid [String] caseid @param lnum [Integer] log it was recorded @return [Case] the case @raise [Error::NotFound] if not case not found
# File lib/icfs/api.rb, line 294 def case_read(cid, lnum=0) if lnum != 0 json = @store.case_read(cid, lnum) return Items.parse(json, 'case', Items::ItemCase) end if !@cases.key?(cid) json = @cache.case_read(cid) cur = Items.parse(json, 'case', Items::ItemCase) @cases[cid] = cur end return @cases[cid] end
Search for a case
@param query [Hash] a query
# File lib/icfs/api.rb, line 507 def case_search(query) Items.validate(query, 'Case Search', ValCaseSearch) @cache.case_search(query) end
Read a current
@param cid [String] caseid
# File lib/icfs/api.rb, line 468 def current_read(cid) al = access_list(cid) if !al.include?(ICFS::PermRead) raise(Error::Perms, 'missing perms: %s' % ICFS::PermRead) end json = @cache.current_read(cid) return Items.parse(json, 'current', Items::ItemCurrent) end
Read an entry
@param cid [String] caseid @param enum [Integer] the entry number @param lnum [Integer] the log number or 0 for current @raise [Error::NotFound] if it does not exist @raise [Error::Perms] if user does not have permissions
# File lib/icfs/api.rb, line 340 def entry_read(cid, enum, lnum=0) # get access list and current entry al = access_list(cid) json = @cache.entry_read(cid, enum) ec = Items.parse(json, 'entry', Items::ItemEntry) # see if we can read the entry need = Set.new need.add( ICFS::PermRead ) unless _can_read?(cid, ec['action'] ) need.merge(ec['perms']) if ec['perms'] need.subtract(al) unless need.empty? raise(Error::Perms, 'missing perms: %s' % need.to_a.sort.join(', ') ) end # return requested version if( lnum == 0 || ec['log'] == lnum ) return ec else json = @store.entry_read(cid, enum, lnum) return Items.parse(json, 'entry', Items::ItemEntry) end end
Search for entries
# File lib/icfs/api.rb, line 584 def entry_search(query) Items.validate(query, 'Entry Search', ValEntrySearch) # check permissions # - have global search permissions / read access to the case # - are searching for an action they can read unless( _search?(query) || (query[:caseid] && query[:action] && tasked?(query[:caseid], query[:action]))) raise(Error::Perms, 'Does not have permission to search') end # run the query res = @cache.entry_search(query) # check perms for each entry res[:list].each do |se| ent = se[:object] # can not read the case/action - basically nothing unless _can_read?(ent[:caseid], ent[:action]) ent[:time] = nil ent[:title] = nil ent[:perms] = nil ent[:action] = nil ent[:tags] = nil ent[:files] = nil ent[:stats] = nil se[:snippet] = nil next end # can read the case/action, missing perms for this entry # leave time, perms, and action al = access_list(ent[:caseid]) if !(Set.new(ent[:perms]) - al).empty? ent[:title] = nil ent[:tags] = nil ent[:files] = nil ent[:stats] = nil se[:snippet] = nil end end return res end
Read a file
@param cid [String] caseid @param enum [Integer] the entry number @param lnum [Integer] the log number @param fnum [Integer] the file number @raise [Error::NotFound] if it does not exist @raise [Error::Perms] if user does not have permissions
# File lib/icfs/api.rb, line 377 def file_read(cid, enum, lnum, fnum) entry_read(cid, enum) fi = @store.file_read(cid, enum, lnum, fnum) raise(Error::NotFound, 'file not found') if !fi return fi end
Read an index
@param cid [String] @param xnum [Integer] the index number @param lnum [Integer] the log number @raise [Error::NotFound] if it does not exist @raise [Error::Perms] if user does not have permissions
# File lib/icfs/api.rb, line 441 def index_read(cid, xnum, lnum=0) # get access list al = access_list(cid) if !al.include?(ICFS::PermRead) raise(Error::Perms, 'missing perms: %s' % ICFS::PermRead ) end # read curent index json = @cache.index_read(cid, xnum) xc = Items.parse(json, 'index', Items::ItemIndex) # return the requested version if( lnum == 0 || xc['log'] == lnum ) return xc else json = @store.index_read(cid, xnum, lnum) return Items.parse(json, 'index', Items::ItemIndex) end end
Search for indexes
# File lib/icfs/api.rb, line 718 def index_search(query) Items.validate(query, 'Index Search', ValIndexSearch) # permissions check # - have global search permissions / read access to the case unless _search?(query) raise(Error::Perms, 'Do not have permission to search') end # run the query res = @cache.index_search(query) # check perms for each index res[:list].each do |se| idx = se[:object] unless access_list(idx[:caseid]).include?(ICFS::PermRead) idx[:title] = nil idx[:tags] = nil end end return res end
Read a log
@param cid [String] caseid @param lnum [Integer] log number @raise [Error::NotFound] if log is not found @raise [Error::Perms] if user does not have permissions
# File lib/icfs/api.rb, line 317 def log_read(cid, lnum) # get access list al = access_list(cid) if !al.include?(ICFS::PermRead) raise(Error::Perms, 'missing perms: %s' % ICFS::PermRead) end # read json = @cache.log_read(cid, lnum) return Items.parse(json, 'log', Items::ItemLog) end
Search for a log
@param query [Hash] a query
# File lib/icfs/api.rb, line 545 def log_search(query) Items.validate(query, 'Log Search', ValLogSearch) @cache.log_search(query) end
Write items to a case
@param ent [Hash] Entry to record, optional @param act [Hash, Nilclass] Action to record, optional @param idx [Hash, Nilclass] Index to record, optional @param cse [Hash, Nilclass] Case to record, optional
# File lib/icfs/api.rb, line 1059 def record(ent, act, idx, cse) #################### # Sanity checks # form & content if ent if (act || idx || cse) Items.validate(ent, 'New Entry', Items::ItemEntryNew) else Items.validate(ent, 'Editable Entry', Items::ItemEntryEdit) end end Items.validate(act, 'action', Items::ItemActionEdit) if act Items.validate(idx, 'index', Items::ItemIndexEdit) if idx Items.validate(cse, 'case', Items::ItemCaseEdit) if cse # get caseid cid ||= ent['caseid'] if ent cid ||= cse['caseid'] if cse cid ||= act['caseid'] if act cid ||= idx['caseid'] if idx unless cid raise(Error::Values, 'No caseid provided') end # no conflicting caseids if( (cse && cse['caseid'] && cse['caseid'] != cid) || (act && act['caseid'] && act['caseid'] != cid) || (idx && idx['caseid'] && idx['caseid'] != cid) ) raise(Error::Values, 'Conflicting caseids provided') end # no changing the action if( act && ent && ent['action'] && act['action'] && act['action'] != ent['action'] ) raise(Error::Conflict, 'May not change entry\'s action') end # access users/roles/groups are valid if cse cse['access'].each do |acc| acc['grant'].each do |gnt| urg = @users.read(gnt) if !urg raise(Error::NotFound, 'User/role/group %s not found' % gnt) end end end end # tasking users/roles are valid if act act['tasks'].each_index do |ix| next if ix == 0 tsk = act['tasks'][ix] ur = @users.read(tsk['assigned']) if !ur raise(Error::NotFound, 'User/role %s not found' % tsk['assigned']) end type = ur['type'] if type != 'user' && type != 'role' raise(Error::Values, 'Not a user or role: %s' % tsk['assigned']) end end end #################### # Prep # entry if ent ent['icfs'] = 1 ent['tags'] ||= [ ] ent['user'] = @user ent['caseid'] = cid files, fhash = _pre_files(ent) else files = [] end # action if act ent['tags'] << ICFS::TagAction if ent act['icfs'] = 1 act['caseid'] = cid act['tasks'].each do |tk| tk['tags'] ||= [ ICFS::TagNone ] end end # index if idx ent['tags'] << ICFS::TagIndex if ent idx['icfs'] = 1 idx['caseid'] = cid idx['tags'] ||= [ ICFS::TagNone ] end # case if cse ent['tags'] << ICFS::TagCase if ent cse['icfs'] = 1 cse['caseid'] = cid cse['tags'] ||= [ ICFS::TagNone ] end # log log = { 'icfs' => 1, 'caseid' => cid, 'user' => @user, } log['files_hash'] = fhash if fhash # no tags ent['tags'] = [ ICFS::TagNone ] if ent && ent['tags'].empty? # current nxt = { 'icfs' => 1, 'caseid' => cid, } #################### # Write # take lock @cache.lock_take(cid) begin now = Time.now.to_i #################### # get prior items & numbers # current json = @cache.current_read(cid) cur = Items.parse(json, 'current', Items::ItemCurrent) # entry if ent if ent['entry'] enum = ent['entry'] json = @cache.entry_read(cid, enum) ent_pri = Items.parse(json, 'entry', Items::ItemEntry) nxt['entry'] = cur['entry'] else enum = cur['entry'] + 1 nxt['entry'] = enum end else nxt['entry'] = cur['entry'] end # action if ent_pri && ent_pri['action'] anum = ent_pri['action'] elsif act && act['action'] anum = act['action'] elsif ent && ent['action'] anum = ent['action'] end if anum json = @cache.action_read(cid, anum) act_pri = Items.parse(json, 'action', Items::ItemAction) nxt['action'] = cur['action'] elsif act anum = cur['action'] + 1 nxt['action'] = anum else nxt['action'] = cur['action'] end # index if idx if idx['index'] xnum = idx['index'] nxt['index'] = cur['index'] else xnum = cur['index'] + 1 nxt['index'] = xnum end else xnum = nil nxt['index'] = cur['index'] end # case cse_pri = case_read(cid) al = access_list(cid) # log lnum = cur['log'] + 1 nxt['log'] = lnum #################### # Checks perms = Set.new # entry perms.merge(ent['perms']) if ent && ent['perms'] if ent_pri # must have those perms perms.add(ent_pri['perms']) if ent_pri['perms'] # may not change action if ent_pri['action'] && (ent['action'] != ent_pri['action']) raise(Error::Conflict, 'May not change entry\'s action') end # may not remove or add action, index, case tags if( (ent_pri['tags'].include?(ICFS::TagAction) != ent['tags'].include?(ICFS::TagAction) ) || (ent_pri['tags'].include?(ICFS::TagIndex) != ent['tags'].include?(ICFS::TagIndex) ) || (ent_pri['tags'].include?(ICFS::TagCase) != ent['tags'].include?(ICFS::TagCase) ) ) raise(Error::Conflict, 'May not change entry\'s special tags') end end # action if act pri_tsk = act_pri ? act_pri['tasks'] : [] cur_tsk = act['tasks'] act_open = cur_tsk[0]['status'] # not allowed to delete tasks if pri_tsk.size > cur_tsk.size raise(Error::Conflict, 'May not delete tasks') end # check each task perm_act = al.include?(ICFS::PermAction) tasked = false cur_tsk.each_index do |ix| ct = cur_tsk[ix] pt = pri_tsk[ix] # may not delete a tasking if pt && pt['assigned'] != ct['assigned'] raise(Error::Conflict, 'May not delete task') end # new taskings require action to be open if !pt && !act_open raise(Error::Value, 'New tasks require the action be open') end # may not have a task open if action is closed if ct['status'] && !act_open raise(Error::Value, 'Open tasks on closed action') end # can set any values for our tasks if @ur.include?(ct['assigned']) || (ix == 0 && perm_act ) tasked = true next end # must be flagged if new tasking or re-opening if !ct['flag'] && (!pt || (ct['status'] && !pt['status'])) raise(Error::Value, 'New or re-opened taskings must flag') end # no changing other's taskings, no deflagging, and no # closing task without action if pt && ( (pt['title'] != ct['title']) || (pt['time'] != ct['time']) || (pt['tags'] != ct['tags']) || (pt['flag'] && !ct['flag']) || (pt['status'] && !ct['status'] && !perm_act) ) raise(Error::Value, 'May not change other\'s tasks') end end # new tasks or changes to other's tasks if !act_pri || !tasked perms.add( ICFS::PermAction ) end end # no checks for index # case if cse # no changing template unless cse['template'] == cse_pri['template'] raise(Error::Conflict, 'May not change template status') end # manage required perms.add( ICFS::PermManage ) if cse end # write unless a case or pre-existing action unless cse || act_pri perms.add( ICFS::PermWrite ) end # permissions perms_miss = perms - al unless perms_miss.empty? raise(Error::Perms, 'Missing perms: %s' % perms_miss.to_a.sort.join(', ') ) end #################### # Items # entry if ent ent['entry'] = enum ent['log'] = lnum ent['time'] ||= now ent['action'] = anum if act if idx if ent['index'] ent['index'] << xnum else ent['index'] = [ xnum ] end end ent['index'].sort!.uniq! if ent['index'] ent['files'].each{|fi| fi['log'] ||= lnum } if ent['files'] eitem = Items.generate(ent, 'entry', Items::ItemEntry) log['entry'] = { 'num' => enum, 'hash' => ICFS.hash(eitem) } end # action if act act['action'] = anum act['log'] = lnum act['entry'] = enum if ent aitem = Items.generate(act, 'action', Items::ItemAction) log['action'] = { 'num' => anum, 'hash' => ICFS.hash(aitem) } end # index if idx idx['index'] = xnum idx['log'] = lnum idx['entry'] = enum if ent xitem = Items.generate(idx, 'index', Items::ItemIndex) log['index'] = { 'num' => xnum, 'hash' => ICFS.hash(xitem) } end # case if cse cse['log'] = lnum cse['entry'] = enum if ent citem = Items.generate(cse, 'case', Items::ItemCase) log['case'] = { 'set' => true, 'hash' => ICFS.hash(citem), } end # log log['log'] = lnum log['prev'] = cur['hash'] log['time'] = now litem = Items.generate(log, 'log', Items::ItemLog) nxt['hash'] = ICFS.hash(litem) # next nitem = Items.generate(nxt, 'current', Items::ItemCurrent) #################### # Write # entry if ent @cache.entry_write(cid, enum, eitem) @store.entry_write(cid, enum, lnum, eitem) end # action if act @cache.action_write(cid, anum, aitem) @store.action_write(cid, anum, lnum, aitem) end # index if idx @cache.index_write(cid, xnum, xitem) @store.index_write(cid, xnum, lnum, xitem) end # case if cse @cache.case_write(cid, citem) @store.case_write(cid, lnum, citem) end # log @cache.log_write(cid, lnum, litem) @store.log_write(cid, lnum, litem) # current @cache.current_write(cid, nitem) # release the lock ensure @cache.lock_release(cid) end # write the files files.each_index{|ix| @store.file_write(cid, enum, lnum, ix+1, files[ix]) } end
Reset the cached cases and access
# File lib/icfs/api.rb, line 160 def reset @cases = {} @access = {} @actions = {} @tasked = {} end
Analyze stats
# File lib/icfs/api.rb, line 760 def stats(query) Items.validate(query, 'Stats Search', ValStatsSearch) # permissions check # - have global search permissions / read access to the case # - are searching for a user/role/group you have unless _search?(query) || (query[:credit] && @urg.include?(query[:credit])) raise(Error::Perms, 'Do not have permissions to search') end @cache.stats(query) end
Get a stats list
@param cid [String] caseid @return [Set<String>] the stats, global and case @raise [Error::NotFound] if case not found
# File lib/icfs/api.rb, line 183 def stats_list(cid) cse = case_read(cid) stats = Set.new stats.merge( cse['stats'] ) if cse['stats'] stats.merge( @gstats ) return stats end
See if we are tasked
# File lib/icfs/api.rb, line 226 def tasked?(cid, anum) id = '%s.%d' % [cid, anum] unless @tasked.key?(id) act = _action_read(cid, anum) tasked = false act['tasks'].each do |tk| if @ur.include?(tk['assigned']) tasked = true break end end @tasked[id] = tasked end return @tasked[id] end
Get a tempfile
# File lib/icfs/api.rb, line 171 def tempfile @store.tempfile end
Set the user
@param uname [String] the user name
# File lib/icfs/api.rb, line 71 def user=(uname) @user = uname.dup.freeze urgp = @users.read(uname) raise(Error::NotFound, 'User name not found') if !urgp raise(Error::Value, 'Not a user') if urgp['type'] != 'user' if urgp['roles'] @roles = urgp['roles'].map{|rn| rn.freeze }.freeze else @roles = [].freeze end if urgp['groups'] @groups = urgp['groups'].map{ |gn| gn.freeze }.freeze else @groups = [].freeze end if urgp['perms'] @perms = urgp['perms'].map{|pn| pn.freeze }.freeze else @perms = [].freeze end @urg = Set.new @urg.add @user @urg.merge @roles @urg.merge @groups @urg.freeze @ur = Set.new @ur.add @user @ur.merge @roles @ur.freeze @config.load(@user) if @config reset end
Flush any user caching
# File lib/icfs/api.rb, line 111 def user_flush() @users.flush(@user) self.user = @user end
Private Instance Methods
Read an action
Internal version.
# File lib/icfs/api.rb, line 390 def _action_read(cid, anum) id = '%s.%d' % [cid, anum] unless @actions.key?(id) json = @cache.action_read(cid, anum) act = Items.parse(json, 'action', Items::ItemAction) @actions[id] = act end return @actions[id] end
Check if we can read an entry or action
# File lib/icfs/api.rb, line 248 def _can_read?(cid, anum) # have read permission on the case or # are assigned to the action if access_list(cid).include?( ICFS::PermRead ) return true elsif anum && tasked?(cid, anum) return true else return false end # handle an action that isn't found rescue Error::NotFound return false end
Assemble files before taking the lock
# File lib/icfs/api.rb, line 1492 def _pre_files(ent) files = [] if ent.key?('files') fhash = [] ent['files'].each do |at| if at.key?('temp') fi = at['temp'] at.delete('temp') files << fi at['num'] = files.size fhash << ICFS.hash_temp(fi) end end fhash = nil if fhash.empty? end return [files, fhash] end
Check if we have search permissions for a case
# File lib/icfs/api.rb, line 270 def _search?(query) if( @perms.include?(ICFS::PermSearch) || ( query[:caseid] && access_list(query[:caseid]).include?(ICFS::PermRead) ) ) return true else return false end end