local cjson = require "cjson" local shallow_copy = require("kong.tools.table").shallow_copy local setmetatable = setmetatable local tostring = tostring local ipairs = ipairs local table = table local type = type -- Get an array of SNI names from either -- an array(sort) or ngx.null(return {}) -- Returns an error if the list has duplicates -- Returns nil if input is falsy. local function parse_name_list(input, errors) local name_list if type(input) == "table" then name_list = shallow_copy(input) elseif input == ngx.null then name_list = {} else return nil end local found = {} for _, name in ipairs(name_list) do if found[name] then local err_t = errors:schema_violation({ snis = name .. " is duplicated", }) return nil, tostring(err_t), err_t end found[name] = true end table.sort(name_list) return setmetatable(name_list, cjson.array_mt) end local _Certificates = {} -- Creates a certificate -- If the provided cert has a field called "snis" it will be used to generate server -- names associated to the cert, after being parsed by parse_name_list. -- Returns a certificate with the snis sorted alphabetically. function _Certificates:insert(cert, options) local name_list, err, err_t = parse_name_list(cert.snis, self.errors) if err then return nil, err, err_t end if name_list then local ok, err, err_t = self.db.snis:check_list_is_new(name_list) if not ok then return nil, err, err_t end end cert, err, err_t = self.super.insert(self, cert, options) if not cert then return nil, err, err_t end cert.snis = name_list or cjson.empty_array if name_list then local ok, err, err_t = self.db.snis:insert_list(cert, name_list, options) if not ok then return nil, err, err_t end end return cert end -- Update override -- If the cert has a "snis" attribute it will be used to update the SNIs -- associated to the cert. -- * If the cert had any names associated which are not on `snis`, they will be -- removed. -- * Any new certificates will be added to the db. -- Returns an error if any of the new certificates where already assigned to a cert different -- from the one identified by cert_pk function _Certificates:update(cert_pk, cert, options) local name_list, err, err_t = parse_name_list(cert.snis, self.errors) if err then return nil, err, err_t end if name_list then local ok, err, err_t = self.db.snis:check_list_is_new(name_list, cert_pk.id) if not ok then return nil, err, err_t end end cert, err, err_t = self.super.update(self, cert_pk, cert, options) if err then return nil, err, err_t end if name_list then cert.snis = name_list local ok, err, err_t = self.db.snis:update_list(cert_pk, name_list) if not ok then return nil, err, err_t end else cert.snis, err, err_t = self.db.snis:list_for_certificate(cert_pk) if not cert.snis then return nil, err, err_t end end return cert end -- Upsert override function _Certificates:upsert(cert_pk, cert, options) local name_list, err, err_t = parse_name_list(cert.snis, self.errors) if err then return nil, err, err_t end if name_list then local ok, err, err_t = self.db.snis:check_list_is_new(name_list, cert_pk.id) if not ok then return nil, err, err_t end end cert, err, err_t = self.super.upsert(self, cert_pk, cert, options) if err then return nil, err, err_t end if name_list then cert.snis = name_list local ok, err, err_t = self.db.snis:update_list(cert_pk, name_list) if not ok then return nil, err, err_t end else cert.snis, err, err_t = self.db.snis:list_for_certificate(cert_pk) if not cert.snis then return nil, err, err_t end end return cert end -- Returns the certificate identified by cert_pk but adds the -- `snis` pseudo attribute to it. It is an array of strings -- representing the SNIs associated to the certificate. function _Certificates:select_with_name_list(cert_pk, options) local cert, err, err_t = self:select(cert_pk, options) if err_t then return nil, err, err_t end if not cert then local err_t = self.errors:not_found(cert_pk) return nil, tostring(err_t), err_t end cert.snis, err, err_t = self.db.snis:list_for_certificate(cert_pk) if err_t then return nil, err, err_t end return cert end -- Returns a page of certificates, each with the `snis` pseudo-attribute -- associated to them. This method does N+1 queries, but for now we are limited -- by the DAO's select options (we can't query for "all the SNIs for this -- list of certificate ids" in one go). function _Certificates:page(size, offset, options) local certs, err, err_t, offset = self.super.page(self, size, offset, options) if not certs then return nil, err, err_t end for i=1, #certs do local cert = certs[i] local snis, err, err_t = self.db.snis:list_for_certificate(cert) if not snis then return nil, err, err_t end cert.snis = snis end return certs, nil, nil, offset end -- Overrides the default delete function by cascading-deleting all the SNIs -- associated to the certificate function _Certificates:delete(cert_pk, options) local name_list, err, err_t = self.db.snis:list_for_certificate(cert_pk) if not name_list then return nil, err, err_t end local ok, err, err_t = self.db.snis:delete_list(name_list) if not ok then return nil, err, err_t end return self.super.delete(self, cert_pk, options) end return _Certificates