simple-gnupg-keyserver 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -1
- data/History.txt +0 -2
- data/Manifest.txt +4 -1
- data/README.rdoc +45 -40
- data/Rakefile +1 -0
- data/lib/simpleHKP/echo.rb +49 -8
- data/lib/simpleHKP/identities.rb +240 -0
- data/lib/simpleHKP/keys.rb +239 -0
- data/lib/simpleHKP/server.rb +349 -0
- data/lib/simpleHKP.rb +10 -489
- metadata +13 -9
data/.gitignore
CHANGED
data/Manifest.txt
CHANGED
data/README.rdoc
CHANGED
@@ -1,32 +1,22 @@
|
|
1
1
|
= rGem-simple-gnupg-keyserver
|
2
2
|
|
3
|
-
|
3
|
+
code :: https://github.com/stephengaito/rGem-simple-gnupg-keyserver
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
SimpleHKP is a simple Ruby/rack based GnuPG HKP key server.
|
8
|
-
|
9
|
-
Its sole task is to supply a limited number of GnuPG _public_ keys
|
10
|
-
to/from MonkeySphere on a limited number of machines/servers. It is
|
11
|
-
_not_ meant to server hundreds of keys, so it is not meant as a truely
|
12
|
-
public key server.
|
13
|
-
|
14
|
-
If you need to run your own _large_ scale production Key Sever then use
|
15
|
-
{SKS}[https://bitbucket.org/skskeyserver/sks-keyserver/wiki/Home]. You
|
16
|
-
might want to read Paul Bauer's {How To Setup A Free PGP Key Server in
|
17
|
-
Ubuntu}[http://www.bauer-power.net/2010/05/how-to-setup-free-pgp-key-server-in.html].
|
5
|
+
docs :: https://stephengaito.github.io/rGem-simple-gnupg-keyserver
|
18
6
|
|
19
|
-
|
7
|
+
== DESCRIPTION:
|
20
8
|
|
21
|
-
|
22
|
-
|
9
|
+
SimpleHKP is a simple Ruby/rack based GnuPG extendedHKP key and
|
10
|
+
identity server packaged as a standard Ruby Gem.
|
23
11
|
|
24
|
-
|
12
|
+
As such it conforms to an {extended
|
13
|
+
version}[http://stephengaito.github.io/rGem-simple-gnupg-keyserver/] of
|
14
|
+
{the OpenPGP HTTP Keyserver Protocol (HKP)
|
15
|
+
(draft-shaw-openpgp-hkp-00.txt)}[http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00].
|
25
16
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
which are matched against each key's key data.
|
17
|
+
The {associated
|
18
|
+
documentation}[https://stephengaito.github.io/rGem-simple-gnupg-keyserver]
|
19
|
+
provides more detail.
|
30
20
|
|
31
21
|
=== Security:
|
32
22
|
|
@@ -47,6 +37,13 @@ This rack application is intentionally as self contained and simple as
|
|
47
37
|
possible. If you have any security concerns, this rack application is
|
48
38
|
easily readable.
|
49
39
|
|
40
|
+
When used with SimpleMonkey, this SimpleHKP acts like a distributed
|
41
|
+
password file. All important (non-public identity) details are
|
42
|
+
encrypted, however, the collection of servers, users and their
|
43
|
+
associated roles are visibly in the "public domain". It is important to
|
44
|
+
run your own collection of SimpleHKP servers *inside* a controlled
|
45
|
+
domain or VPN.
|
46
|
+
|
50
47
|
== SYNOPSIS:
|
51
48
|
|
52
49
|
A typical rackup.ru file might be:
|
@@ -68,13 +65,14 @@ key/value pairs.
|
|
68
65
|
The current default options are:
|
69
66
|
|
70
67
|
defaultOptions = {
|
71
|
-
'debug' => false,
|
72
|
-
'title' => 'SimpleHKP'
|
73
|
-
'simpleHKPdir' => 'simpleHKP',
|
74
|
-
'keyDir' => 'keys',
|
75
|
-
'
|
76
|
-
'
|
77
|
-
'
|
68
|
+
'debug' => false, # should debug output to logged?
|
69
|
+
'title' => 'SimpleHKP' # the title used by the default headerHTML
|
70
|
+
'simpleHKPdir' => 'simpleHKP', # base disk path to simpleHKP disk space
|
71
|
+
'keyDir' => 'keys', # subdir to key storage directory
|
72
|
+
'idDir' => 'identities', # subdir to identity storage directory
|
73
|
+
'mediaDir' => 'media', # subdir to any css, js, images etc
|
74
|
+
'htmlDir' => 'html', # subdir to html partials
|
75
|
+
'mimeMap' => { # a file ext to mime mapping
|
78
76
|
'css' => 'text/css',
|
79
77
|
'html' => 'text/html',
|
80
78
|
'js' => 'text/javascript'
|
@@ -86,14 +84,16 @@ the use of humans:
|
|
86
84
|
|
87
85
|
* header.html
|
88
86
|
* defaultBody.html
|
89
|
-
*
|
90
|
-
*
|
87
|
+
* lookupKeysForm.html
|
88
|
+
* lookupIdentitiesForm.html
|
89
|
+
* uploadKeyForm.html
|
90
|
+
* uploadIdentityForm.html
|
91
91
|
* footer.html
|
92
92
|
|
93
93
|
== SYNCHRONIZATION
|
94
94
|
|
95
|
-
A 'simpleHKP/echo' class has been added which knows how to echo
|
96
|
-
one key server to another.
|
95
|
+
A 'simpleHKP/echo' class has been added which knows how to echo
|
96
|
+
keys/identities from one key server to another.
|
97
97
|
|
98
98
|
So that for example the following ruby script could be placed into one
|
99
99
|
of your machine's /etc/daily directories and the script would ensure
|
@@ -101,13 +101,13 @@ all key servers are synchronized daily.
|
|
101
101
|
|
102
102
|
#!/usr/bin/env ruby
|
103
103
|
|
104
|
-
# A simple key server sychronization example
|
104
|
+
# A simple key/identity server sychronization example
|
105
105
|
#
|
106
106
|
# We use a hub-spoke model. We choose one "hub" key server to act as
|
107
|
-
# the master key server and amalgamate all of the keys from
|
108
|
-
# key server back to the hub key server. We then push the
|
109
|
-
# keys in the hub back to the spokes, so that
|
110
|
-
# servers have the same keys.
|
107
|
+
# the master key server and amalgamate all of the keys/identities from
|
108
|
+
# the spoke key server back to the hub key server. We then push the
|
109
|
+
# amalgamated keys/identities in the hub back to the spokes, so that
|
110
|
+
# after two passes, all key servers have the same keys/identities.
|
111
111
|
|
112
112
|
require 'simpleHKP/echo'
|
113
113
|
|
@@ -118,13 +118,13 @@ all key servers are synchronized daily.
|
|
118
118
|
'spokeKeyServer3'
|
119
119
|
]
|
120
120
|
|
121
|
-
# Start by bringing all spoke keys into the hub
|
121
|
+
# Start by bringing all spoke keys/identities into the hub
|
122
122
|
#
|
123
123
|
spokeKeyServers.each do | aKeyServer |
|
124
124
|
SimpleHKPEcho.echoFromTo(aKeyServer, hubKeyServer)
|
125
125
|
end
|
126
126
|
|
127
|
-
# now send the amalgamated hub keys back to each spoke
|
127
|
+
# now send the amalgamated hub keys/identities back to each spoke
|
128
128
|
#
|
129
129
|
spokeKeyServers.each do | aKeyServer |
|
130
130
|
SimpleHKPEcho.echoFromTo(hubKeyServer, aKeyServer)
|
@@ -137,6 +137,10 @@ The SimpleHKPEcho.echoFromTo method fails gracefully if a given key
|
|
137
137
|
server is offline, by simply returning. In the example above, all other
|
138
138
|
echo pairs (from, to) will be tried.
|
139
139
|
|
140
|
+
To just echo either the keys or identities you can use
|
141
|
+
SimpleHKPEcho.echoKeysFromTo or SimpleHKPEcho.echoIdentitiesFromTo
|
142
|
+
respectively.
|
143
|
+
|
140
144
|
== REQUIREMENTS:
|
141
145
|
|
142
146
|
There are explicitly no external Ruby requirements other than Ruby and
|
@@ -175,3 +179,4 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
175
179
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
176
180
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
177
181
|
|
182
|
+
|
data/Rakefile
CHANGED
data/lib/simpleHKP/echo.rb
CHANGED
@@ -2,10 +2,11 @@ require 'uri'
|
|
2
2
|
require 'net/http'
|
3
3
|
require 'pp'
|
4
4
|
|
5
|
-
# This code provides a simple way to ensure keys are
|
6
|
-
# between a pair of key servers.
|
5
|
+
# This code provides a simple way to ensure keys/identities are
|
6
|
+
# synchronized between a pair of key/identity servers.
|
7
7
|
|
8
|
-
# It conforms to
|
8
|
+
# It conforms to our simple extension to:
|
9
|
+
# The OpenPGP HTTP Keyserver Protocol (HKP)
|
9
10
|
# draft-shaw-openpgp-hkp-00.txt
|
10
11
|
# http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00
|
11
12
|
|
@@ -48,7 +49,7 @@ class SimpleHKPEcho
|
|
48
49
|
aKeyServerStr
|
49
50
|
end
|
50
51
|
|
51
|
-
def
|
52
|
+
def echoKeysFromTo(fromKeyServer, toKeyServer, options = {})
|
52
53
|
begin
|
53
54
|
debug = options.delete('debug')
|
54
55
|
puts fromKeyServer if debug
|
@@ -59,7 +60,7 @@ class SimpleHKPEcho
|
|
59
60
|
puts toKeyServer if debug
|
60
61
|
|
61
62
|
keys = Array.new
|
62
|
-
url = URI.parse(fromKeyServer+'/lookup?search=&op=index&options=mr')
|
63
|
+
url = URI.parse(fromKeyServer+'/pks/lookup?search=&op=index&options=mr')
|
63
64
|
response = Net::HTTP.get_response(url)
|
64
65
|
response.body.each_line do | aLine |
|
65
66
|
next unless aLine =~ /^pub/
|
@@ -68,19 +69,59 @@ class SimpleHKPEcho
|
|
68
69
|
pp keys if debug
|
69
70
|
keys.each do | aKey |
|
70
71
|
keyData = ""
|
71
|
-
url = URI.parse(fromKeyServer+"/lookup?op=get&options=mr&search=#{aKey}")
|
72
|
+
url = URI.parse(fromKeyServer+"/pks/lookup?op=get&options=mr&search=#{aKey}")
|
72
73
|
response = Net::HTTP.get_response(url)
|
73
74
|
keyData = response.body
|
74
75
|
puts aKey if debug
|
75
76
|
puts keyData if debug
|
76
|
-
url = URI.parse(toKeyServer+'/add')
|
77
|
+
url = URI.parse(toKeyServer+'/pks/add')
|
77
78
|
Net::HTTP.post_form(url, { 'keytext' => keyData })
|
78
79
|
end
|
79
|
-
rescue
|
80
|
+
rescue Exception => ex
|
80
81
|
puts "Cound not echo keys from #{fromKeyServer} to #{toKeyServer}"
|
82
|
+
puts ex
|
81
83
|
end
|
82
84
|
end
|
83
85
|
|
86
|
+
def echoIdentitiesFromTo(fromKeyServer, toKeyServer, options = {})
|
87
|
+
begin
|
88
|
+
debug = options.delete('debug')
|
89
|
+
puts fromKeyServer if debug
|
90
|
+
fromKeyServer = convertToHttp(fromKeyServer)
|
91
|
+
puts fromKeyServer if debug
|
92
|
+
puts toKeyServer if debug
|
93
|
+
toKeyServer = convertToHttp(toKeyServer)
|
94
|
+
puts toKeyServer if debug
|
95
|
+
|
96
|
+
identities = Array.new
|
97
|
+
url = URI.parse(fromKeyServer+'/pis/lookup?search=&op=index&options=mr')
|
98
|
+
response = Net::HTTP.get_response(url)
|
99
|
+
response.body.each_line do | aLine |
|
100
|
+
next unless aLine =~ /^idn/
|
101
|
+
identities.push(aLine.split(/:/)[1])
|
102
|
+
end
|
103
|
+
pp identities if debug
|
104
|
+
identities.each do | anIdFile |
|
105
|
+
idData = ""
|
106
|
+
url = URI.parse(fromKeyServer+"/pis/lookup?op=get&options=mr&search=#{anIdFile}")
|
107
|
+
response = Net::HTTP.get_response(url)
|
108
|
+
idData = response.body
|
109
|
+
puts anIdFile if debug
|
110
|
+
puts idData if debug
|
111
|
+
url = URI.parse(toKeyServer+'/pis/add')
|
112
|
+
Net::HTTP.post_form(url, { 'keytext' => idData })
|
113
|
+
end
|
114
|
+
rescue Exception => ex
|
115
|
+
puts "Cound not echo identities from #{fromKeyServer} to #{toKeyServer}"
|
116
|
+
puts ex
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def echoFromTo(fromKeyServer, toKeyServer, options = {})
|
121
|
+
echoKeysFromTo(fromKeyServer, toKeyServer, options)
|
122
|
+
echoIdentitiesFromTo(fromKeyServer, toKeyServer, options)
|
123
|
+
end
|
124
|
+
|
84
125
|
end
|
85
126
|
|
86
127
|
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
|
2
|
+
module SimpleHKP
|
3
|
+
|
4
|
+
module Identities
|
5
|
+
|
6
|
+
def loadIdentityFile(fileName)
|
7
|
+
identityContents = ''
|
8
|
+
begin
|
9
|
+
identityContents = File.open(fileName, 'r').read
|
10
|
+
rescue Exception => e
|
11
|
+
puts "Error reading file #{fileName}: #{e.message}"
|
12
|
+
end
|
13
|
+
identityContents
|
14
|
+
end
|
15
|
+
|
16
|
+
def getIdentityMetaData(fileContents)
|
17
|
+
metaData = Hash.new
|
18
|
+
begin
|
19
|
+
if fileContents =~ /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
|
20
|
+
metaData = SafeYAML.load($1)
|
21
|
+
end
|
22
|
+
rescue SyntaxError => e
|
23
|
+
puts "YAML Exception reading #{fileName}: #{e.message}"
|
24
|
+
end
|
25
|
+
|
26
|
+
if !metaData.has_key?('role') ||
|
27
|
+
!metaData.has_key?('userKeyID') ||
|
28
|
+
metaData['userKeyID'] !~ /^\h+$/ then
|
29
|
+
puts "WARNING the identity file has NO role or userKeyID or a malformed userKeyID"
|
30
|
+
return ''
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# normalize the meta data with any missing (but useful) values
|
35
|
+
#
|
36
|
+
metaData['userID'] = metaData['userKeyID'] unless
|
37
|
+
metaData.has_key?('userID')
|
38
|
+
metaData['algorithm'] = 'unknown' unless metaData.has_key?('algorithm')
|
39
|
+
metaData['created'] = Date.today.strftime("%Y%m%d") unless
|
40
|
+
metaData.has_key?('created')
|
41
|
+
metaData['expires'] = Date.today.strftime("%Y%m%d") unless
|
42
|
+
metaData.has_key?('expires')
|
43
|
+
metaData['flags'] = '' unless metaData.has_key?('flags')
|
44
|
+
#
|
45
|
+
# compute the idFile for searching
|
46
|
+
#
|
47
|
+
metaData['identity'] = metaData['role']+'-'+metaData['userKeyID']
|
48
|
+
#
|
49
|
+
# wrap the whole meta data into a searchable string
|
50
|
+
#
|
51
|
+
metaData['searchData'] = metaData.to_s
|
52
|
+
metaData
|
53
|
+
end
|
54
|
+
|
55
|
+
def indexIdentityFiles
|
56
|
+
puts "SimpleHKP: indexing identities" if @debug
|
57
|
+
@idMetaData = Hash.new
|
58
|
+
Dir.glob(@idDir+'/**/*.asc') do | aFile |
|
59
|
+
idFile = File.basename(aFile, '.*')
|
60
|
+
puts aFile if @debug
|
61
|
+
puts idFile if @debug
|
62
|
+
identityFile = loadIdentityFile(aFile)
|
63
|
+
metaData = getIdentityMetaData(identityFile)
|
64
|
+
if metaData.empty? then
|
65
|
+
puts "WARNING the identity file [#{aFile}] is malformed!"
|
66
|
+
else
|
67
|
+
@idMetaData[idFile] = metaData
|
68
|
+
end
|
69
|
+
puts @idMetaData[idFile] if @debug
|
70
|
+
end
|
71
|
+
# File.open(@idIndex, 'w') do | indexFile |
|
72
|
+
# indexFile.puts "# identity metaData index created: #{DateTime.now}"
|
73
|
+
# indexFile.write(YAML.dump(@idMetaData))
|
74
|
+
# end
|
75
|
+
puts "SimpleHKP: finished indexing identities" if @debug
|
76
|
+
end
|
77
|
+
|
78
|
+
def loadIdentities
|
79
|
+
@idLookupData = Hash.new
|
80
|
+
puts "SimpleHKP: loading identities" if @debug
|
81
|
+
indexIdentityFiles
|
82
|
+
pp @idMetaData
|
83
|
+
puts "SimpleHKP: finished loading identities" if @debug
|
84
|
+
end
|
85
|
+
|
86
|
+
def storeIdentity(env)
|
87
|
+
#
|
88
|
+
# decode the post data
|
89
|
+
#
|
90
|
+
keys = URI.decode_www_form(env['rack.input'].read)
|
91
|
+
#
|
92
|
+
# ensure we are being sent a key
|
93
|
+
#
|
94
|
+
return replyBadRequest("No keytext field in post data") unless
|
95
|
+
keys[0][0] =~ /keytext/
|
96
|
+
pp keys[0][1] if @debug
|
97
|
+
metaData = getIdentityMetaData(keys[0][1])
|
98
|
+
pp metaData
|
99
|
+
return replyBadRequest("malformed identity metaData") if metaData.empty?
|
100
|
+
#
|
101
|
+
# store this key as a flat file with the name of the keyID.asc
|
102
|
+
#
|
103
|
+
idFile = metaData['role']+'-'+metaData['userKeyID']
|
104
|
+
idFileName = @idDir+'/'+idFile+'.asc'
|
105
|
+
idChanged = 'unchanged'
|
106
|
+
if !File.exists?(idFileName) || keys[0][1] != File.read(idFileName) then
|
107
|
+
idChanged = 'changed'
|
108
|
+
File.write(idFileName, keys[0][1])
|
109
|
+
@idMetaData[idFile] = metaData
|
110
|
+
end
|
111
|
+
pp @idMetaData
|
112
|
+
#
|
113
|
+
# return OK
|
114
|
+
#
|
115
|
+
@statusCode = 200
|
116
|
+
@body << @headerHtml
|
117
|
+
@body << "<h1 class=\"simpleHKP-storedIdentity\">Stored identity: [#{idFile}]</h1>"
|
118
|
+
@body << "<p>identity #{idChanged}</p>"
|
119
|
+
@body << "<h2 class=\"simpleHKP-identityDataID\">Identity data for identity: [#{idFile}]</h2>"
|
120
|
+
@body << '<pre class="simpleHKP-identityData">'+keys[0][1]+"</pre>\n"
|
121
|
+
@body << @footer
|
122
|
+
end
|
123
|
+
|
124
|
+
def lookUpIdentity(queryString)
|
125
|
+
return replyBadRequest("No search field in queryString") unless
|
126
|
+
queryString.has_key?('search')
|
127
|
+
#
|
128
|
+
# normalize the search string
|
129
|
+
#
|
130
|
+
searchString = queryString['search']
|
131
|
+
searchRegexp = Regexp.new(searchString,
|
132
|
+
Regexp::IGNORECASE | Regexp::MULTILINE)
|
133
|
+
puts searchRegexp if @debug
|
134
|
+
#
|
135
|
+
# (linearly) look through the hash of known identities
|
136
|
+
# looking for the FIRST match
|
137
|
+
#
|
138
|
+
idFile = nil
|
139
|
+
pp @idMetaData
|
140
|
+
@idMetaData.each_pair do | idKey, idData |
|
141
|
+
next unless idData['searchData'] =~ searchRegexp
|
142
|
+
puts "FOUND #{idKey} (#{idData})" if @debug
|
143
|
+
idFile = idKey
|
144
|
+
break
|
145
|
+
end
|
146
|
+
return replyNotFound if idFile.nil?
|
147
|
+
|
148
|
+
idFileName = @idDir+'/'+idFile+'.asc'
|
149
|
+
if queryString.has_key?('options') &&
|
150
|
+
queryString['options'] == 'mr' then
|
151
|
+
#
|
152
|
+
# return the key data in machine readable format
|
153
|
+
#
|
154
|
+
@header = {
|
155
|
+
'Content-Type' => 'text/plain; charset=utf-8',
|
156
|
+
'Content-Disposition' =>
|
157
|
+
'attachment; filename=' + File.basename(idFileName)
|
158
|
+
}
|
159
|
+
puts @header if @debug
|
160
|
+
@body << File.read(idFileName)
|
161
|
+
else
|
162
|
+
#
|
163
|
+
# return the key data for a human to read
|
164
|
+
#
|
165
|
+
@body << @headerHtml
|
166
|
+
@body << @lookupIdentitiesForm
|
167
|
+
@body << '<h1 class="simpleHKP-identityAscIdFile">Identity: '+idFile+'</h1>'
|
168
|
+
@body << '<h2 class="simpleHKP-identityAscTitle">Identity contents:</h2>'
|
169
|
+
@body << '<pre class="simpleHKP-identityAsc">'
|
170
|
+
@body << File.read(idFileName)
|
171
|
+
@body << '</pre>'
|
172
|
+
@body << @footer
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def indexIdentities(queryString)
|
177
|
+
return replyBadRequest("No search field in queryString") unless
|
178
|
+
queryString.has_key?('search')
|
179
|
+
#
|
180
|
+
# normalize the search string
|
181
|
+
#
|
182
|
+
searchString = queryString['search']
|
183
|
+
searchRegexp = Regexp.new(searchString,
|
184
|
+
Regexp::IGNORECASE | Regexp::MULTILINE)
|
185
|
+
puts searchRegexp if @debug
|
186
|
+
#
|
187
|
+
# accumulate the idFiles of any identities that match the search
|
188
|
+
#
|
189
|
+
idFiles = Array.new
|
190
|
+
pp @idMetaData
|
191
|
+
@idMetaData.each_pair do | idKey, idData |
|
192
|
+
next unless idData['searchData'] =~ searchRegexp
|
193
|
+
puts "FOUND #{idKey} (#{idData})" if @debug
|
194
|
+
idFiles.push(idKey)
|
195
|
+
end
|
196
|
+
|
197
|
+
if queryString.has_key?('options') &&
|
198
|
+
queryString['options'] == 'mr' then
|
199
|
+
#
|
200
|
+
# return a machine readable list of the dFiles found
|
201
|
+
#
|
202
|
+
@header = { 'Content-Type' => 'text/plain' }
|
203
|
+
@body << "info:1:#{idFiles.size}\n"
|
204
|
+
idFiles.each do | anIdFile |
|
205
|
+
next unless @idMetaData.has_key?(anIdFile)
|
206
|
+
idData = @idMetaData[anIdFile]
|
207
|
+
idnData = [ "idn" ]
|
208
|
+
idnData << anIdFile
|
209
|
+
idnData << idData['role']
|
210
|
+
idnData << idData['userID'].gsub(/:/, '\x3a')
|
211
|
+
idnData << idData['algorithm']
|
212
|
+
idnData << idData['created']
|
213
|
+
idnData << idData['expires']
|
214
|
+
idnData << idData['flags']
|
215
|
+
@body << idnData.join(':')
|
216
|
+
@body << "\n"
|
217
|
+
end
|
218
|
+
else
|
219
|
+
#
|
220
|
+
# return a (simple) human readable list of the keys found
|
221
|
+
#
|
222
|
+
@body << @headerHtml
|
223
|
+
@body << @lookupIdentitiesForm
|
224
|
+
@body << '<h1 class="simpleHKP-searchString">Identities matching: ['+searchString+']</h1><ul class="simpleHKP-searchList">'
|
225
|
+
idFiles.each do | anIdFile |
|
226
|
+
next unless @idMetaData.has_key?(anIdFile)
|
227
|
+
idData = @idMetaData[anIdFile]
|
228
|
+
idStr = " <li class=\"simpleHKP-searchItem\"><a href=\"/pis/lookup?op=get&search=#{anIdFile}\">"
|
229
|
+
idStr << anIdFile+': '+idData['userID']+' ('+idData['role']+')'
|
230
|
+
idStr << "</a></li>\n"
|
231
|
+
@body << idStr
|
232
|
+
end
|
233
|
+
@body << '</ul>'
|
234
|
+
@body << @footer
|
235
|
+
end
|
236
|
+
pp @body if @debug
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
end
|