<match_string>open-ils.cstore.direct.actor.user.create</match_string>
<match_string>open-ils.cstore.direct.actor.user.update</match_string>
<match_string>open-ils.cstore.direct.actor.user.delete</match_string>
+ <match_string>open-ils.search.z3950.apply_credentials</match_string>
</log_protect>
</shared>
</config>
my %services;
my $default_service;
+__PACKAGE__->register_method(
+ method => 'apply_credentials',
+ api_name => 'open-ils.search.z3950.apply_credentials',
+ signature => {
+ desc => "Apply credentials for a Z39.50 server",
+ params => [
+ {desc => 'Authtoken', type => 'string'},
+ {desc => 'Z39.50 Source (server) name', type => 'string'},
+ {desc => 'Context org unit', type => 'number'},
+ {desc => 'Username', type => 'string'},
+ {desc => 'Password', type => 'string'}
+ ],
+ return => {
+ desc => 'Event; SUCCESS on success, other event type on error'
+ }
+ }
+);
+
+sub apply_credentials {
+ my ($self, $client, $auth, $source, $ctx_ou, $username, $password) = @_;
+
+ my $e = new_editor(authtoken => $auth, xact => 1);
+
+ return $e->die_event unless
+ $e->checkauth and
+ $e->allowed('ADMIN_Z3950_SOURCE', $ctx_ou);
+
+ $e->json_query({from => [
+ 'config.z3950_source_credentials_apply',
+ $source, $ctx_ou, $username, $password
+ ]}) or return $e->die_event;
+
+ $e->commit;
+
+ return OpenILS::Event->new('SUCCESS');
+}
+
+
__PACKAGE__->register_method(
method => 'do_class_search',
my $limit = $$args{limit} || 10;
my $offset = $$args{offset} || 0;
- my $username = $$args{username} || "";
- my $password = $$args{password} || "";
+ my $editor = new_editor(authtoken => $auth);
+ return $editor->event unless
+ $editor->checkauth and
+ $editor->allowed('REMOTE_Z3950_QUERY', $editor->requestor->ws_ou);
+
+ my $creds = $editor->json_query({from => [
+ 'config.z3950_source_credentials_lookup',
+ $$args{service}, $editor->requestor->ws_ou
+ ]})->[0] || {};
- my $tformat = $services{$args->{service}}->{transmission_format} || $output;
+ # use the caller-provided username/password if offered.
+ # otherwise, use the stored credentials.
+ my $username = $$args{username} || $creds->{username} || "";
+ my $password = $$args{password} || $creds->{password} || "";
- my $editor = new_editor(authtoken => $auth);
- return $editor->event unless $editor->checkauth;
- return $editor->event unless $editor->allowed('REMOTE_Z3950_QUERY');
+ my $tformat = $services{$args->{service}}->{transmission_format} || $output;
$logger->info("z3950: connecting to server $host:$port:$db as $username");
CONSTRAINT z_code_format_once_per_source UNIQUE (code,format,source)
);
+CREATE TABLE config.z3950_source_credentials (
+ id SERIAL PRIMARY KEY,
+ owner INTEGER NOT NULL, -- REFERENCES actor.org_unit(id),
+ source TEXT NOT NULL REFERENCES config.z3950_source(name),
+ username TEXT,
+ password TEXT,
+ CONSTRAINT czsc_source_once_per_lib UNIQUE (source, owner)
+);
+
CREATE TABLE config.i18n_locale (
code TEXT PRIMARY KEY,
marc_code TEXT NOT NULL, -- should exist in config.coded_value_map WHERE ctype = 'item_lang'
ALTER TABLE config.z3950_source ADD CONSTRAINT use_perm_fkey FOREIGN KEY (use_perm) REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
+ALTER TABLE config.z3950_source_credentials ADD CONSTRAINT z3950_source_creds_owner_fkey FOREIGN KEY (owner) REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+
ALTER TABLE config.org_unit_setting_type_log ADD CONSTRAINT config_org_unit_setting_type_log_fkey FOREIGN KEY (org) REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE config.filter_dialog_filter_set
$BODY$ LANGUAGE plperlu IMMUTABLE STRICT COST 100;
-- user activity functions --
+
+
+-- find the most relevant set of credentials for the Z source and org
+CREATE OR REPLACE FUNCTION config.z3950_source_credentials_lookup
+ (source TEXT, owner INTEGER)
+ RETURNS config.z3950_source_credentials AS $$
+
+ SELECT creds.*
+ FROM config.z3950_source_credentials creds
+ JOIN actor.org_unit aou ON (aou.id = creds.owner)
+ JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
+ WHERE creds.source = $1 AND creds.owner IN (
+ SELECT id FROM actor.org_unit_ancestors($2)
+ )
+ ORDER BY aout.depth DESC LIMIT 1;
+
+$$ LANGUAGE SQL STABLE;
+
+-- since we are not exposing config.z3950_source_credentials
+-- via the IDL, providing a stored proc gives us a way to
+-- set values in the table via cstore
+CREATE OR REPLACE FUNCTION config.z3950_source_credentials_apply
+ (src TEXT, org INTEGER, uname TEXT, passwd TEXT)
+ RETURNS VOID AS $$
+BEGIN
+ PERFORM 1 FROM config.z3950_source_credentials
+ WHERE owner = org AND source = src;
+
+ IF FOUND THEN
+ IF COALESCE(uname, '') = '' AND COALESCE(passwd, '') = '' THEN
+ DELETE FROM config.z3950_source_credentials
+ WHERE owner = org AND source = src;
+ ELSE
+ UPDATE config.z3950_source_credentials
+ SET username = uname, password = passwd
+ WHERE owner = org AND source = src;
+ END IF;
+ ELSE
+ IF COALESCE(uname, '') <> '' OR COALESCE(passwd, '') <> '' THEN
+ INSERT INTO config.z3950_source_credentials
+ (source, owner, username, password)
+ VALUES (src, org, uname, passwd);
+ END IF;
+ END IF;
+END;
+$$ LANGUAGE PLPGSQL;
+
+
--- /dev/null
+
+BEGIN;
+
+CREATE TABLE config.z3950_source_credentials (
+ id SERIAL PRIMARY KEY,
+ owner INTEGER NOT NULL REFERENCES actor.org_unit(id),
+ source TEXT NOT NULL REFERENCES config.z3950_source(name),
+ -- do some Z servers require a username but no password or vice versa?
+ username TEXT,
+ password TEXT,
+ CONSTRAINT czsc_source_once_per_lib UNIQUE (source, owner)
+);
+
+-- find the most relevant set of credentials for the Z source and org
+CREATE OR REPLACE FUNCTION config.z3950_source_credentials_lookup
+ (source TEXT, owner INTEGER)
+ RETURNS config.z3950_source_credentials AS $$
+
+ SELECT creds.*
+ FROM config.z3950_source_credentials creds
+ JOIN actor.org_unit aou ON (aou.id = creds.owner)
+ JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
+ WHERE creds.source = $1 AND creds.owner IN (
+ SELECT id FROM actor.org_unit_ancestors($2)
+ )
+ ORDER BY aout.depth DESC LIMIT 1;
+
+$$ LANGUAGE SQL STABLE;
+
+-- since we are not exposing config.z3950_source_credentials
+-- via the IDL, providing a stored proc gives us a way to
+-- set values in the table via cstore
+CREATE OR REPLACE FUNCTION config.z3950_source_credentials_apply
+ (src TEXT, org INTEGER, uname TEXT, passwd TEXT)
+ RETURNS VOID AS $$
+BEGIN
+ PERFORM 1 FROM config.z3950_source_credentials
+ WHERE owner = org AND source = src;
+
+ IF FOUND THEN
+ IF COALESCE(uname, '') = '' AND COALESCE(passwd, '') = '' THEN
+ DELETE FROM config.z3950_source_credentials
+ WHERE owner = org AND source = src;
+ ELSE
+ UPDATE config.z3950_source_credentials
+ SET username = uname, password = passwd
+ WHERE owner = org AND source = src;
+ END IF;
+ ELSE
+ IF COALESCE(uname, '') <> '' OR COALESCE(passwd, '') <> '' THEN
+ INSERT INTO config.z3950_source_credentials
+ (source, owner, username, password)
+ VALUES (src, org, uname, passwd);
+ END IF;
+ END IF;
+END;
+$$ LANGUAGE PLPGSQL;
+
+
+COMMIT;
</div>
</div>
<div>
- <button onClick="location.href = location.href.replace(/\/[^\/]+$/, '')">[% l('Return to Sources') %]</button>
+ <button onClick="location.href = location.href.replace(/\/[^\/]+$/, '')">
+ [% l('Return to Sources') %]
+ </button>
</div>
<br/>
+
+ <div>
+ [% l('Credentials for ') %]
+ <select dojoType="openils.widget.OrgUnitFilteringSelect"
+ jsId='z39ContextSelector'
+ searchAttr='shortname'
+ labelAttr='shortname'>
+ </select>
+ [% l('Username') %]
+ <input type='text' id='z39-creds-username'/>
+ [% l('Password') %]
+ <input type='password' id='z39-creds-password'/>
+ <button id='z39-creds-button' onclick='applyCreds()'>
+ [% l('Apply Credentials') %]
+ </button>
+ <button id='z39-creds-clear' onclick='applyCreds(1)'>
+ [% l('Clear Stored Credentials') %]
+ </button>
+ </div>
+
+ <br/>
<table
id="zaGrid"
readOnly : true
};
+ // draw the credentials context org unit selector
+ new openils.User().buildPermOrgSelector(
+ 'ADMIN_Z3950_SOURCE', z39ContextSelector);
+
} else {
zsGrid.loadAll({order_by:{czs : 'name'}});
}
}
+function applyCreds(clear) {
+ dojo.byId('z39-creds-button').disabled = true;
+ dojo.byId('z39-creds-clear').disabled = true;
+ fieldmapper.standardRequest(
+ ['open-ils.search', 'open-ils.search.z3950.apply_credentials'],
+ { async : true,
+ params : [
+ openils.User.authtoken,
+ sourceCode,
+ z39ContextSelector.attr('value'),
+ clear ? '' : dojo.byId('z39-creds-username').value,
+ clear ? '' : dojo.byId('z39-creds-password').value
+ ],
+ oncomplete : function(r) {
+ dojo.byId('z39-creds-password').value = '';
+ dojo.byId('z39-creds-button').disabled = false;
+ dojo.byId('z39-creds-clear').disabled = false;
+ openils.Util.readResponse(r);
+ }
+ }
+ );
+}
+
function formatSourceName(val) {
return '<a href="' + location.href + '/' + escape(val) + '">' + val + '</a>';
}
--- /dev/null
+Storing Z39.50 Server Credentials
+=================================
+
+In the Z39.50 configuration interface, staff now have the option to apply
+z39.50 login credentials to each Z39.50 server at different levels of the
+org unit hierarchy (similar to org unit settings). When credentials are set
+for a Z39 server, searches against the z39 server will used the stored
+credentials, unless other credentials are provided by the caller, in which
+case the caller credentials are used.
+
+For security purposes, passwords may not be retrieved or reported on by staff.
+Staff can only apply new values for credentials or clear existing ones.
+