Editing
Module:Sensitive IP addresses/API
From Thetacola Wiki
Jump to navigation
Jump to search
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
-- This module provides functions for handling sensitive IP addresses. -- Load modules local mIP = require('Module:IP') local IPAddress = mIP.IPAddress local Subnet = mIP.Subnet local IPv4Collection = mIP.IPv4Collection local IPv6Collection = mIP.IPv6Collection -- Lazily load the jf-JSON module local JSON ------------------------------------------------------------------------------- -- Helper functions ------------------------------------------------------------------------------- local function deepCopy(val) -- Make a deep copy of a value, but don't worry about self-references or -- metatables as mw.clone does. If a table in val has a self-reference, -- you will get an infinite loop, so don't do that. if type(val) == 'table' then local ret = {} for k, v in pairs(val) do ret[k] = deepCopy(v) end return ret else return val end end local function deepCopyInto(source, dest) -- Do a deep copy of a source table into a destination table, ignoring -- self-references and metatables. If a table in source has a self-reference -- you will get an infinite loop. for k, v in pairs(source) do if type(v) == 'table' then dest[k] = {} deepCopyInto(v, dest[k]) else dest[k] = v end end end local function removeDuplicates(t) -- Return a copy of an array with duplicate values removed. local keys, ret = {}, {} for i, v in ipairs(t) do if not keys[v] then table.insert(ret, v) keys[v] = true end end return ret end ------------------------------------------------------------------------------- -- SensitiveEntity class -- A country or organization for which blocks must be handled with care. -- Media organizations may inspect block messages for IP addresses and ranges -- belonging to these entities and those messages may end up in the press. ------------------------------------------------------------------------------- local SensitiveEntity = {} SensitiveEntity.__index = SensitiveEntity SensitiveEntity.reasons = { -- The reasons that an entity may be sensitive. Used to verify data in -- Module:Sensitive IP addresses/list. political = true, technical = true, } do -- Private methods local function addRanges(self, key, collectionConstructor, ranges) if ranges and ranges[1] then self[key] = collectionConstructor() for i, range in ipairs(ranges) do self[key]:addSubnet(Subnet.new(range)) end end end -- Constructor function SensitiveEntity.new(data) local self = setmetatable({}, SensitiveEntity) -- Set data self.data = data addRanges(self, 'v4Collection', IPv4Collection.new, data.ipv4Ranges) addRanges(self, 'v6Collection', IPv6Collection.new, data.ipv6Ranges) return self end end function SensitiveEntity:matchesIPOrRange(str) -- Returns true, matchObj, queryObj if there is a match for the IP address -- string or CIDR range str in the sensitive entity. Returns false -- otherwise. matchObj is the Subnet object that was matched, and queryObj -- is the IPAddress or Subnet object corresponding to the input string. -- Get the IPAddress or Subnet object for str local isIP, isSubnet, obj isIP, obj = pcall(IPAddress.new, str) if isIP and not obj then isIP = false end if not isIP then isSubnet, obj = pcall(Subnet.new, str) if not isSubnet or not obj then error(string.format( "'%s' is not a valid IP address or CIDR string", str ), 2) end end -- Try matching the object to the appropriate collection local function isInCollection(collection, obj, isIP) if isIP then if collection then local isMatch, matchObj = collection:containsIP(obj) return isMatch, matchObj, obj else return false end else if collection then local isMatch, matchObj = collection:overlapsSubnet(obj) return isMatch, matchObj, obj else return false end end end if obj:isIPv4() then return isInCollection(self.v4Collection, obj, isIP) else return isInCollection(self.v6Collection, obj, isIP) end end ------------------------------------------------------------------------------- -- Sensitive IP API ------------------------------------------------------------------------------- -- This API is used by external tools and gadgets, so it should be kept -- backwards-compatible. Clients query the API with a query table, and the -- API returns a response table. The response table is available as a Lua table -- for other Lua modules, and as JSON for external clients. -- Example query tables: -- -- Query IP addresses and ranges: -- { -- test = {'1.2.3.4', '4.5.6.0/24', '2001:db8::ff00:12:3456', '2001:db8::ff00:12:0/112'}, -- } -- -- Query specific entities: -- { -- entities = {'ussenate', 'ushr'} -- } -- -- Query all entities: -- { -- entities = {'all'} -- } -- -- Query all entities and format the result as a JSON string: -- { -- entities = {'all'}, -- format = 'json' -- } -- -- Combined query: -- { -- test = {'1.2.3.4', '4.5.6.0/24', '2001:db8::ff00:12:3456', '2001:db8::ff00:12:0/112'}, -- entities = {'ussenate', 'ushr'} -- } -- Example response: -- -- { -- sensitiveips = { -- matches = { -- { -- ip = '1.2.3.4', -- type = 'ip', -- ['ip-version'] = 'IPv4', -- ['matches-range'] = '1.2.3.0/24', -- ['entity-id'] = 'entityid' -- }, -- { -- range = '4.5.6.0/24', -- type = 'range', -- ['ip-version'] = 'IPv4', -- ['matches-range'] = '4.5.0.0/16', -- ['entity-id'] = 'entityid' -- } -- }, -- ['matched-ranges'] = { -- ['1.2.3.0/24'] = { -- range = '1.2.3.0/24', -- ['ip-version'] = 'IPv4', -- ['entity-id'] = 'entityid' -- }, -- ['4.5.0.0/16'] = { -- range = '4.5.0.0/16', -- ['ip-version'] = 'IPv4', -- ['entity-id'] = 'entityid' -- } -- }, -- entities = { -- ['entityid'] = { -- id = 'entityid', -- name = 'The entity name', -- description = 'A description of the entity', -- ['ipv4-ranges'] = { -- '1.2.3.0/24', -- '4.5.0.0/16' -- '6.7.0.0/16' -- }, -- ['ipv6-ranges'] = { -- '2001:db8::ff00:12:0/112' -- }, -- notes = 'Notes about the entity or its ranges' -- } -- } -- ['entity-ids'] = { -- 'entityid' -- } -- } -- } -- -- Response with errors: -- -- { -- error = { -- code = 'example-error', -- info = 'There was an error', -- ['*'] = 'See https://en.wikipedia.org/wiki/Module:Sensitive_IP_addresses for API usage' -- } -- } local function query(options) -- Make entity objects local entities, entityIndexes = {}, {} local data = mw.loadData('Module:Sensitive IP addresses/list') for i, entityData in ipairs(data) do entities[entityData.id] = SensitiveEntity.new(entityData) entityIndexes[entityData.id] = i -- Keep track of the original order end local function makeError(code, info, format) local ret = {['error'] = { code = code, info = info, ['*'] = 'See https://en.wikipedia.org/wiki/Module:Sensitive_IP_addresses/API for API usage', }} if format == 'json' then return mw.text.jsonEncode(ret) else return ret end end -- Construct result local result = { matches = {}, ['matched-ranges'] = {}, entities = {}, ['entity-ids'] = {} } if type(options) ~= 'table' then return makeError( 'sipa-options-type-error', string.format( "type error in argument #1 of 'query' (expected table, received %s)", type(options) ) ) elseif not options.test and not options.entities then return makeError( 'sipa-blank-options', "the options table didn't contain a 'test' or an 'entities' key", options.format ) end if options.test then if type(options.test) ~= 'table' then return makeError( 'sipa-test-type-error', string.format( "'test' options key was type %s (expected table)", type(options.test) ), options.format ) end for i, testString in ipairs(options.test) do if type(testString) ~= 'string' then return makeError( 'sipa-test-string-type-error', string.format( "type error in item #%d in the 'test' array (expected string, received %s)", i, type(testString) ), options.format ) end for k, entity in pairs(entities) do -- Try to match the range with the current sensitive entity. local success, isMatch, matchObj, queryObj = pcall( entity.matchesIPOrRange, entity, testString ) if not success then -- The string was invalid. return makeError( 'sipa-invalid-test-string', string.format( "test string #%d '%s' was not a valid IP address or CIDR string", i, testString ), options.format ) end if isMatch then -- The string was a sensitive IP address or subnet. -- Add match data local match = {} -- Quick and dirty hack to find if queryObj is an IPAddress object. local isIP = queryObj.getNextIP ~= nil and queryObj.isInSubnet ~= nil if isIP then match.type = 'ip' match.ip = tostring(queryObj) else match.type = 'range' match.range = tostring(queryObj) end match['ip-version'] = queryObj:getVersion() match['matches-range'] = matchObj:getCIDR() match['entity-id'] = entity.data.id table.insert(result.matches, match) -- Add the matched range data. result['matched-ranges'][match['matches-range']] = { range = match['matches-range'], ['ip-version'] = match['ip-version'], ['entity-id'] = match['entity-id'], } -- Add the entity data for the entity we matched. result.entities[match['entity-id']] = deepCopy( entities[match['entity-id']].data ) -- Add the entity ID for the entity we matched. table.insert(result['entity-ids'], match['entity-id']) end end end end -- Add entity data requested explicitly. if options.entities then if type(options.entities) ~= 'table' then return makeError( 'sipa-entities-type-error', string.format( "'entities' options key was type %s (expected table)", type(options.test) ), options.format ) end -- Check the type of all the entity strings, and check if 'all' has -- been specified. local isAll = false for i, entityString in ipairs(options.entities) do if type(entityString) ~= 'string' then return makeError( 'sipa-entity-string-type-error', string.format( "type error in item #%d in the 'entities' array (expected string, received %s)", i, type(entityString) ), options.format ) end if entityString == 'all' then isAll = true end end if isAll then -- Add all the entity data. -- As the final result will contain all the entity data, we can -- just create the entities and entity-ids subtables from scratch -- without worrying about what any existing values might be. result.entities = {} result['entity-ids'] = {} for i, entityData in ipairs(data) do result.entities[entityData.id] = deepCopy(entityData) result['entity-ids'][i] = entityData.id end else -- Add data for the entities specified. -- Insert the entity and entity-id subtables if they aren't already -- present. for i, entityString in ipairs(options.entities) do if entities[entityString] then result.entities[entityString] = deepCopy( entities[entityString].data ) table.insert(result['entity-ids'], entityString) end end result['entity-ids'] = removeDuplicates(result['entity-ids']) table.sort(result['entity-ids'], function(s1, s2) return entityIndexes[s1] < entityIndexes[s2] end) end end -- Add any missing reason fields from entities. for id, entityData in pairs(result.entities) do entityData.reason = entityData.reason or 'political' end -- Wrap the result in an outer layer like the MediaWiki Action API does. result = {sensitiveips = result} if options.format == 'json' then -- Load jf-JSON JSON = JSON or require('Module:jf-JSON') JSON.strictTypes = true -- Necessary for correct blank-object encoding -- Decode a skeleton result JSON string. This ensures that blank objects -- are re-encoded as blank objects and not as blank arrays. local jsonResult = JSON:decode([[{"sensitiveips": { "matches": [], "matched-ranges": {}, "entities": {}, "entity-ids": [] }}]]) for i, key in ipairs{'matches', 'matched-ranges', 'entities', 'entity-ids'} do deepCopyInto(result.sensitiveips[key], jsonResult.sensitiveips[key]) end return JSON:encode(jsonResult) elseif options.format == nil or options.format == 'lua' then return result elseif type(options.format) ~= 'string' then return makeError( 'sipa-format-type-error', string.format( "'format' options key was type %s (expected string or nil)", type(options.format) ) ) else return makeError( 'sipa-invalid-format', string.format( "invalid format '%s' (expected 'json' or 'lua')", type(options.format) ) ) end end -------------------------------------------------------------------------------- -- Exports -------------------------------------------------------------------------------- local p = {} function p._isValidSensitivityReason(s) -- Return true if s is a valid sensitivity reason; otherwise return false. return s ~= nil and SensitiveEntity.reasons[s] ~= nil end function p._getSensitivityReasons(separator, conjunction) -- Return an string of valid sensitivity reasons, ordered alphabetically. -- The reasons are separated by an optional separator; if conjunction is -- specified it is used instead of the last separator, as in -- mw.text.listToText. -- Get an array of valid sensitivity reasons. local reasons = {} for reason in pairs(SensitiveEntity.reasons) do reasons[#reasons + 1] = reason end table.sort(reasons) -- Convert arguments if we are being called from wikitext. if type(separator) == 'table' and type(separator.getParent) == 'function' then -- separator is a frame object local frame = separator separator = frame.args[1] conjunction = frame.args[2] end -- Return a formatted string return mw.text.listToText(reasons, separator, conjunction) end -- Export the API query function p.query = query return p
Summary:
Please note that all contributions to Thetacola Wiki may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
Project:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Templates used on this page:
Template:Module other
(
edit
)
Template:Module rating
(
edit
)
Template:Ombox
(
edit
)
Template:Sandbox other
(
edit
)
Template:Used in system
(
edit
)
Module:Arguments
(
edit
)
Module:Effective protection level
(
edit
)
Module:High-use
(
edit
)
Module:IP
(
edit
)
Module:Message box
(
edit
)
Module:Message box/configuration
(
edit
)
Module:Message box/ombox.css
(
edit
)
Module:No globals
(
edit
)
Module:Sensitive IP addresses/API
(
edit
)
Module:Sensitive IP addresses/API/doc
(
edit
)
Module:String
(
edit
)
Module:Transclusion count
(
edit
)
Module:Transclusion count/data/S
(
edit
)
Module:Yesno
(
edit
)
Navigation menu
Page actions
Module
Discussion
Read
Edit source
History
Page actions
Module
Discussion
More
Tools
Personal tools
Not logged in
Talk
Contributions
Create account
Log in
Navigation
Main page
Recent changes
Random page
Help about MediaWiki
Search
Tools
What links here
Related changes
Special pages
Page information