local-openid 0.1.1 → 0.2.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/.document +5 -4
- data/.gitignore +12 -0
- data/{LICENSE.txt → COPYING} +0 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +135 -9
- data/LICENSE +5 -0
- data/{README.txt → README} +22 -12
- data/Rakefile +180 -9
- data/bin/local-openid +16 -297
- data/lib/local_openid.rb +302 -0
- data/local-openid.gemspec +34 -0
- metadata +68 -33
- data/History.txt +0 -7
- data/Manifest.txt +0 -10
data/.document
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
2
|
-
LICENSE
|
3
|
-
|
4
|
-
|
1
|
+
NEWS
|
2
|
+
LICENSE
|
3
|
+
ChangeLog
|
4
|
+
README
|
5
|
+
lib/
|
data/.gitignore
CHANGED
data/{LICENSE.txt → COPYING}
RENAMED
File without changes
|
data/GIT-VERSION-GEN
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
GVF=GIT-VERSION-FILE
|
4
|
+
DEF_VER=v0.2.0.GIT
|
5
|
+
|
6
|
+
LF='
|
7
|
+
'
|
8
|
+
|
9
|
+
# First see if there is a version file (included in release tarballs),
|
10
|
+
# then try git-describe, then default.
|
11
|
+
if test -f version
|
12
|
+
then
|
13
|
+
VN=$(cat version) || VN="$DEF_VER"
|
14
|
+
elif test -d .git -o -f .git &&
|
15
|
+
VN=$(git describe --abbrev=4 HEAD 2>/dev/null) &&
|
16
|
+
case "$VN" in
|
17
|
+
*$LF*) (exit 1) ;;
|
18
|
+
v[0-9]*)
|
19
|
+
git update-index -q --refresh
|
20
|
+
test -z "$(git diff-index --name-only HEAD --)" ||
|
21
|
+
VN="$VN-dirty" ;;
|
22
|
+
esac
|
23
|
+
then
|
24
|
+
VN=$(echo "$VN" | sed -e 's/-/./g');
|
25
|
+
else
|
26
|
+
VN="$DEF_VER"
|
27
|
+
fi
|
28
|
+
|
29
|
+
VN=$(expr "$VN" : v*'\(.*\)')
|
30
|
+
|
31
|
+
if test -r $GVF
|
32
|
+
then
|
33
|
+
VC=$(sed -e 's/^GIT_VERSION = //' <$GVF)
|
34
|
+
else
|
35
|
+
VC=unset
|
36
|
+
fi
|
37
|
+
test "$VN" = "$VC" || {
|
38
|
+
echo >&2 "GIT_VERSION = $VN"
|
39
|
+
echo "GIT_VERSION = $VN" >$GVF
|
40
|
+
}
|
data/GNUmakefile
CHANGED
@@ -1,19 +1,145 @@
|
|
1
|
-
all
|
1
|
+
all::
|
2
|
+
RUBY = ruby
|
3
|
+
RAKE = rake
|
4
|
+
RSYNC = rsync
|
5
|
+
GIT_URL = git://git.bogomips.org/local-openid.git
|
6
|
+
|
7
|
+
GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
|
8
|
+
@./GIT-VERSION-GEN
|
9
|
+
-include GIT-VERSION-FILE
|
10
|
+
|
11
|
+
pkg_extra := GIT-VERSION-FILE NEWS ChangeLog
|
12
|
+
manifest: $(pkg_extra)
|
13
|
+
$(RM) .manifest
|
14
|
+
$(MAKE) .manifest
|
15
|
+
|
16
|
+
.manifest:
|
17
|
+
(git ls-files && \
|
18
|
+
for i in $@ $(pkg_extra) $(man1_paths); \
|
19
|
+
do echo $$i; done) | LC_ALL=C sort > $@+
|
20
|
+
cmp $@+ $@ || mv $@+ $@
|
21
|
+
$(RM) $@+
|
22
|
+
|
23
|
+
NEWS: GIT-VERSION-FILE
|
24
|
+
$(RAKE) -s news_rdoc > $@+
|
25
|
+
mv $@+ $@
|
26
|
+
|
27
|
+
SINCE = 0.1.0
|
28
|
+
ChangeLog: LOG_VERSION = \
|
29
|
+
$(shell git rev-parse -q "$(GIT_VERSION)" >/dev/null 2>&1 && \
|
30
|
+
echo $(GIT_VERSION) || git describe)
|
31
|
+
ifneq ($(SINCE),)
|
32
|
+
ChangeLog: log_range = v$(SINCE)..$(LOG_VERSION)
|
33
|
+
endif
|
34
|
+
ChangeLog: GIT-VERSION-FILE
|
35
|
+
@echo "ChangeLog from $(GIT_URL) ($(log_range))" > $@+
|
36
|
+
@echo >> $@+
|
37
|
+
git log $(log_range) | sed -e 's/^/ /' >> $@+
|
38
|
+
mv $@+ $@
|
39
|
+
|
40
|
+
news_atom := http://bogomips.org/local-openid/NEWS.atom.xml
|
41
|
+
cgit_atom := http://git.bogomips.org/cgit/local-openid.git/atom/?h=master
|
42
|
+
atom = <link rel="alternate" title="Atom feed" href="$(1)" \
|
43
|
+
type="application/atom+xml"/>
|
44
|
+
|
45
|
+
doc: .document NEWS ChangeLog
|
46
|
+
find bin lib -type f -name '*.rbc' -exec rm -f '{}' ';'
|
47
|
+
rdoc -a -t "$(shell sed -ne '1s/^= //p' README)"
|
48
|
+
install -m644 COPYING doc/COPYING
|
49
|
+
install -m644 $(shell grep '^[A-Z]' .document) doc/
|
50
|
+
$(RUBY) -i -p -e \
|
51
|
+
'$$_.gsub!("</title>",%q{\&$(call atom,$(cgit_atom))})' \
|
52
|
+
doc/ChangeLog.html
|
53
|
+
$(RUBY) -i -p -e \
|
54
|
+
'$$_.gsub!("</title>",%q{\&$(call atom,$(news_atom))})' \
|
55
|
+
doc/NEWS.html doc/README.html
|
56
|
+
$(RAKE) -s news_atom > doc/NEWS.atom.xml
|
57
|
+
cd doc && ln README.html tmp && mv tmp index.html
|
2
58
|
|
3
59
|
publish_doc:
|
4
60
|
-git set-file-times
|
5
|
-
$(
|
61
|
+
$(RM) -r doc ChangeLog NEWS
|
62
|
+
$(MAKE) doc LOG_VERSION=$(shell git tag -l | tail -1)
|
63
|
+
@awk 'BEGIN{RS="=== ";ORS=""}NR==2{sub(/\n$$/,"");print RS""$$0 }' \
|
64
|
+
< NEWS > doc/LATEST
|
65
|
+
find doc/images doc/js -type f | \
|
66
|
+
TZ=UTC xargs touch -d '1970-01-01 00:00:00' doc/rdoc.css
|
6
67
|
$(MAKE) doc_gz
|
7
|
-
|
68
|
+
chmod 644 $$(find doc -type f)
|
69
|
+
$(RSYNC) -av --delete doc/ bogomips.org:/srv/bogomips/local-openid/
|
8
70
|
git ls-files | xargs touch
|
9
71
|
|
10
|
-
|
11
|
-
|
72
|
+
ifneq ($(VERSION),)
|
73
|
+
rfproject := qrp
|
74
|
+
rfpackage := local-openid
|
75
|
+
pkggem := pkg/$(rfpackage)-$(VERSION).gem
|
76
|
+
pkgtgz := pkg/$(rfpackage)-$(VERSION).tgz
|
77
|
+
release_notes := release_notes-$(VERSION)
|
78
|
+
release_changes := release_changes-$(VERSION)
|
79
|
+
|
80
|
+
release-notes: $(release_notes)
|
81
|
+
release-changes: $(release_changes)
|
82
|
+
$(release_changes):
|
83
|
+
$(RAKE) -s release_changes > $@+
|
84
|
+
$(VISUAL) $@+ && test -s $@+ && mv $@+ $@
|
85
|
+
$(release_notes):
|
86
|
+
GIT_URL=$(GIT_URL) $(RAKE) -s release_notes > $@+
|
87
|
+
$(VISUAL) $@+ && test -s $@+ && mv $@+ $@
|
88
|
+
|
89
|
+
# ensures we're actually on the tagged $(VERSION), only used for release
|
90
|
+
verify:
|
91
|
+
test x"$(shell umask)" = x0022
|
92
|
+
git rev-parse --verify refs/tags/v$(VERSION)^{}
|
93
|
+
git diff-index --quiet HEAD^0
|
94
|
+
test `git rev-parse --verify HEAD^0` = \
|
95
|
+
`git rev-parse --verify refs/tags/v$(VERSION)^{}`
|
96
|
+
|
97
|
+
fix-perms:
|
98
|
+
-git ls-tree -r HEAD | awk '/^100644 / {print $$NF}' | xargs chmod 644
|
99
|
+
-git ls-tree -r HEAD | awk '/^100755 / {print $$NF}' | xargs chmod 755
|
100
|
+
|
101
|
+
gem: $(pkggem)
|
102
|
+
|
103
|
+
install-gem: $(pkggem)
|
104
|
+
gem install $(CURDIR)/$<
|
105
|
+
|
106
|
+
$(pkggem): manifest fix-perms
|
107
|
+
gem build $(rfpackage).gemspec
|
108
|
+
mkdir -p pkg
|
109
|
+
mv $(@F) $@
|
110
|
+
|
111
|
+
$(pkgtgz): distdir = $(basename $@)
|
112
|
+
$(pkgtgz): HEAD = v$(VERSION)
|
113
|
+
$(pkgtgz): manifest fix-perms
|
114
|
+
@test -n "$(distdir)"
|
115
|
+
$(RM) -r $(distdir)
|
116
|
+
mkdir -p $(distdir)
|
117
|
+
tar cf - `cat .manifest` | (cd $(distdir) && tar xf -)
|
118
|
+
cd pkg && tar c $(basename $(@F)) | gzip -9 > $(@F)+
|
119
|
+
mv $@+ $@
|
120
|
+
|
121
|
+
package: $(pkgtgz) $(pkggem)
|
122
|
+
|
123
|
+
test-release: verify package $(release_notes) $(release_changes)
|
124
|
+
release: verify package $(release_notes) $(release_changes)
|
125
|
+
# make tgz release on RubyForge
|
126
|
+
rubyforge add_release -f -n $(release_notes) -a $(release_changes) \
|
127
|
+
$(rfproject) $(rfpackage) $(VERSION) $(pkgtgz)
|
128
|
+
# push gem to Gemcutter
|
129
|
+
gem push $(pkggem)
|
130
|
+
# in case of gem downloads from RubyForge releases page
|
131
|
+
-rubyforge add_file \
|
132
|
+
$(rfproject) $(rfpackage) $(VERSION) $(pkggem)
|
133
|
+
else
|
134
|
+
gem install-gem: GIT-VERSION-FILE
|
135
|
+
$(MAKE) $@ VERSION=$(GIT_VERSION)
|
136
|
+
endif
|
12
137
|
|
13
138
|
# Create gzip variants of the same timestamp as the original so nginx
|
14
139
|
# "gzip_static on" can serve the gzipped versions directly.
|
15
|
-
doc_gz:
|
16
|
-
doc_gz: globs := $(addprefix doc/*.,$(suf)) $(addprefix doc/*/*.,$(suf))
|
17
|
-
doc_gz: docs := $(wildcard $(globs))
|
140
|
+
doc_gz: docs = $(shell find doc -type f ! -regex '^.*\.\(gif\|jpg\|png\|gz\)$$')
|
18
141
|
doc_gz:
|
19
|
-
|
142
|
+
touch doc/NEWS.atom.xml -d "$$(awk 'NR==1{print $$4,$$5,$$6}' NEWS)"
|
143
|
+
for i in $(docs); do \
|
144
|
+
gzip --rsyncable -9 < $$i > $$i.gz; touch -r $$i $$i.gz; done
|
145
|
+
.PHONY: .FORCE-GIT-VERSION-FILE doc manifest
|
data/LICENSE
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
local-openid is copyrighted free software by all contributors, see logs
|
2
|
+
in revision control for names and email addresses of all of them. You
|
3
|
+
can redistribute it and/or modify it under either the terms of the GNU
|
4
|
+
Affero General Public License, version 3. See the link:COPYING file
|
5
|
+
for details.
|
data/{README.txt → README}
RENAMED
@@ -1,9 +1,5 @@
|
|
1
1
|
= local-openid: Single User, Ephemeral OpenID Provider
|
2
2
|
|
3
|
-
* http://bogomips.org/local-openid
|
4
|
-
|
5
|
-
== Description
|
6
|
-
|
7
3
|
local-openid allows users with shell accounts on servers to authenticate
|
8
4
|
with OpenID consumers by editing a YAML file in their home directory
|
9
5
|
instead of authenticating through HTTP/HTTPS.
|
@@ -43,17 +39,35 @@ setup.rb is also provided for non-Rubygems users.
|
|
43
39
|
== Requirements
|
44
40
|
|
45
41
|
local-openid is a small Sinatra application. It requires the Ruby
|
46
|
-
OpenID library (2.x), Sinatra (0
|
42
|
+
OpenID library (2.x), Sinatra (1.0), Rack and any Rack-enabled
|
47
43
|
server. To be useful, it also depends on having a user account on a
|
48
44
|
machine with a publically-accessible IP and DNS name to use as your
|
49
45
|
OpenID identity.
|
50
46
|
|
47
|
+
== Running
|
48
|
+
|
49
|
+
"local-openid" should be installed in your $PATH by RubyGems or
|
50
|
+
setup.rb. It is a Sinatra application and takes the usual
|
51
|
+
command-line arguments. It binds on all addresses (0.0.0.0) and port
|
52
|
+
4567 by default, using the standard WEBrick web server.
|
53
|
+
|
54
|
+
You may specify a different port with the *-p* switch and address with
|
55
|
+
the *-o* switch. The following command will start local-openid on port
|
56
|
+
3000 bound to localhost (useful if behind a reverse proxy like nginx).
|
57
|
+
|
58
|
+
local-openid -o 127.0.0.1 -p 3000
|
59
|
+
|
51
60
|
== Hacking
|
52
61
|
|
53
62
|
I don't have any plans for more development with local-openid. It was
|
54
63
|
after all, just a weekend hack. It does what I want it to and nothing
|
55
64
|
more.
|
56
65
|
|
66
|
+
You can use the {mailing list}[mailto:local.openid@librelist.com] to
|
67
|
+
share ideas, patches, pull requests with other users. Remember, I
|
68
|
+
wrote local-openid because I find the web difficult to use. So I'll
|
69
|
+
only accept communication about local-openid via email :)
|
70
|
+
|
57
71
|
Feel free to fork it and customize it to your needs. Of course, drop me
|
58
72
|
a line if you fix any bugs or notice any security holes in it.
|
59
73
|
|
@@ -69,11 +83,6 @@ You may browse the code from the web and download the latest tarballs here:
|
|
69
83
|
* http://git.bogomips.org/cgit/local-openid.git
|
70
84
|
* http://repo.or.cz/w/local-openid.git (gitweb mirror)
|
71
85
|
|
72
|
-
== License
|
73
|
-
|
74
|
-
Copyright 2009 Eric Wong. It is licensed under the GNU Affero General
|
75
|
-
Public License, version 3. See the LICENSE file for details.
|
76
|
-
|
77
86
|
== Disclaimer
|
78
87
|
|
79
88
|
There is NO WARRANTY whatsoever, implied or otherwise. OpenID may not
|
@@ -84,5 +93,6 @@ credentials when your provider implementation has 99.999% downtime :)
|
|
84
93
|
|
85
94
|
== Contact
|
86
95
|
|
87
|
-
Eric Wong, normalperson@yhbt.net
|
88
|
-
OpenID: http://e.yhbt.net/
|
96
|
+
* Original author: Eric Wong, normalperson@yhbt.net
|
97
|
+
* OpenID: http://e.yhbt.net/
|
98
|
+
* mailing list: local.openid@librelist.com
|
data/Rakefile
CHANGED
@@ -1,12 +1,183 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
autoload :Gem, 'rubygems'
|
3
3
|
|
4
|
-
|
4
|
+
def tags
|
5
|
+
timefmt = '%Y-%m-%dT%H:%M:%SZ'
|
6
|
+
@tags ||= `git tag -l`.split(/\n/).map do |tag|
|
7
|
+
if %r{\Av[\d\.]+\z} =~ tag
|
8
|
+
header, subject, body = `git cat-file tag #{tag}`.split(/\n\n/, 3)
|
9
|
+
header = header.split(/\n/)
|
10
|
+
tagger = header.grep(/\Atagger /).first
|
11
|
+
body ||= "initial"
|
12
|
+
{
|
13
|
+
:time => Time.at(tagger.split(/ /)[-2].to_i).utc.strftime(timefmt),
|
14
|
+
:tagger_name => %r{^tagger ([^<]+)}.match(tagger)[1].strip,
|
15
|
+
:tagger_email => %r{<([^>]+)>}.match(tagger)[1].strip,
|
16
|
+
:id => `git rev-parse refs/tags/#{tag}`.chomp!,
|
17
|
+
:tag => tag,
|
18
|
+
:subject => subject,
|
19
|
+
:body => body,
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end.compact.sort { |a,b| b[:time] <=> a[:time] }
|
23
|
+
end
|
24
|
+
|
25
|
+
cgit_url = "http://git.bogomips.org/cgit/local-openid.git"
|
26
|
+
git_url = ENV['GIT_URL'] || 'git://git.bogomips.org/local-openid.git'
|
27
|
+
|
28
|
+
desc 'prints news as an Atom feed'
|
29
|
+
task :news_atom do
|
30
|
+
require 'nokogiri'
|
31
|
+
new_tags = tags[0,10]
|
32
|
+
puts(Nokogiri::XML::Builder.new do
|
33
|
+
feed :xmlns => "http://www.w3.org/2005/Atom" do
|
34
|
+
id! "http://bogomips.org/local-openid/NEWS.atom.xml"
|
35
|
+
title "local-openid news"
|
36
|
+
subtitle %q{Single User, Ephemeral OpenID Provider}
|
37
|
+
link! :rel => 'alternate', :type => 'text/html',
|
38
|
+
:href => 'http://bogomips.org/local-openid/NEWS.html'
|
39
|
+
updated(new_tags.empty? ? "1970-01-01T00:00:00Z" : new_tags.first[:time])
|
40
|
+
new_tags.each do |tag|
|
41
|
+
entry do
|
42
|
+
title tag[:subject]
|
43
|
+
updated tag[:time]
|
44
|
+
published tag[:time]
|
45
|
+
author {
|
46
|
+
name tag[:tagger_name]
|
47
|
+
email tag[:tagger_email]
|
48
|
+
}
|
49
|
+
url = "#{cgit_url}/tag/?id=#{tag[:tag]}"
|
50
|
+
link! :rel => "alternate", :type => "text/html", :href =>url
|
51
|
+
id! url
|
52
|
+
message_only = tag[:body].split(/\n.+\(\d+\):\n {6}/s).first.strip
|
53
|
+
content({:type =>:text}, message_only)
|
54
|
+
content(:type =>:xhtml) { pre tag[:body] }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end.to_xml)
|
59
|
+
end
|
60
|
+
|
61
|
+
desc 'prints RDoc-formatted news'
|
62
|
+
task :news_rdoc do
|
63
|
+
tags.each do |tag|
|
64
|
+
time = tag[:time].tr!('T', ' ').gsub!(/:\d\dZ/, ' UTC')
|
65
|
+
puts "=== #{tag[:tag].sub(/^v/, '')} / #{time}"
|
66
|
+
puts ""
|
67
|
+
|
68
|
+
body = tag[:body]
|
69
|
+
puts tag[:body].gsub(/^/sm, " ").gsub(/[ \t]+$/sm, "")
|
70
|
+
puts ""
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "print release changelog for Rubyforge"
|
75
|
+
task :release_changes do
|
76
|
+
version = ENV['VERSION'] or abort "VERSION= needed"
|
77
|
+
version = "v#{version}"
|
78
|
+
vtags = tags.map { |tag| tag[:tag] =~ /\Av/ and tag[:tag] }.sort
|
79
|
+
prev = vtags[vtags.index(version) - 1]
|
80
|
+
if prev
|
81
|
+
system('git', 'diff', '--stat', prev, version) or abort $?
|
82
|
+
puts ""
|
83
|
+
system('git', 'log', "#{prev}..#{version}") or abort $?
|
84
|
+
else
|
85
|
+
system('git', 'log', version) or abort $?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
desc "print release notes for Rubyforge"
|
90
|
+
task :release_notes do
|
91
|
+
spec = Gem::Specification.load('local-openid.gemspec')
|
92
|
+
puts spec.description.strip
|
93
|
+
puts ""
|
94
|
+
puts "* #{spec.homepage}"
|
95
|
+
puts "* #{spec.email}"
|
96
|
+
puts "* #{git_url}"
|
97
|
+
|
98
|
+
_, _, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3)
|
99
|
+
print "\nChanges:\n\n"
|
100
|
+
puts body
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "read news article from STDIN and post to rubyforge"
|
104
|
+
task :publish_news do
|
105
|
+
require 'rubyforge'
|
106
|
+
IO.select([STDIN], nil, nil, 1) or abort "E: news must be read from stdin"
|
107
|
+
msg = STDIN.readlines
|
108
|
+
subject = msg.shift
|
109
|
+
blank = msg.shift
|
110
|
+
blank == "\n" or abort "no newline after subject!"
|
111
|
+
subject.strip!
|
112
|
+
body = msg.join("").strip!
|
113
|
+
|
114
|
+
rf = RubyForge.new.configure
|
115
|
+
rf.login
|
116
|
+
rf.post_news('qrp', subject, body)
|
117
|
+
end
|
118
|
+
|
119
|
+
desc "post to RAA"
|
120
|
+
task :raa_update do
|
121
|
+
require 'net/http'
|
122
|
+
require 'net/netrc'
|
123
|
+
rc = Net::Netrc.locate('local-openid-raa') or abort "~/.netrc not found"
|
124
|
+
password = rc.password
|
125
|
+
|
126
|
+
s = Gem::Specification.load('local-openid.gemspec')
|
127
|
+
desc = [ s.description.strip ]
|
128
|
+
desc << ""
|
129
|
+
desc << "* #{s.email}"
|
130
|
+
desc << "* #{git_url}"
|
131
|
+
desc << "* #{cgit_url}"
|
132
|
+
desc = desc.join("\n")
|
133
|
+
uri = URI.parse('http://raa.ruby-lang.org/regist.rhtml')
|
134
|
+
form = {
|
135
|
+
:name => s.name,
|
136
|
+
:short_description => s.summary,
|
137
|
+
:version => s.version.to_s,
|
138
|
+
:status => 'stable',
|
139
|
+
:owner => s.authors.first,
|
140
|
+
:email => s.email,
|
141
|
+
:category_major => 'Application',
|
142
|
+
:category_minor => 'WWW',
|
143
|
+
:url => s.homepage,
|
144
|
+
:download => 'http://rubyforge.org/frs/?group_id=5626',
|
145
|
+
:license => "OpenSource", # AGPLv3, specifically
|
146
|
+
:description_style => 'Plain',
|
147
|
+
:description => desc,
|
148
|
+
:pass => password,
|
149
|
+
:submit => "Update",
|
150
|
+
}
|
151
|
+
res = Net::HTTP.post_form(uri, form)
|
152
|
+
p res
|
153
|
+
puts res.body
|
154
|
+
end
|
155
|
+
|
156
|
+
desc "post to FM"
|
157
|
+
task :fm_update do
|
158
|
+
require 'tempfile'
|
159
|
+
require 'net/http'
|
160
|
+
require 'net/netrc'
|
161
|
+
require 'json'
|
162
|
+
version = ENV['VERSION'] or abort "VERSION= needed"
|
163
|
+
uri = URI.parse('http://freshmeat.net/projects/local-openid/releases.json')
|
164
|
+
rc = Net::Netrc.locate('local-openid-fm') or abort "~/.netrc not found"
|
165
|
+
api_token = rc.password
|
166
|
+
changelog = tags.find { |t| t[:tag] == "v#{version}" }[:body]
|
167
|
+
tmp = Tempfile.new('fm-changelog')
|
168
|
+
tmp.syswrite(changelog)
|
169
|
+
system(ENV["VISUAL"], tmp.path) or abort "#{ENV["VISUAL"]} failed: #$?"
|
170
|
+
changelog = File.read(tmp.path).strip
|
5
171
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
172
|
+
req = {
|
173
|
+
"auth_code" => api_token,
|
174
|
+
"release" => {
|
175
|
+
"tag_list" => "Stable",
|
176
|
+
"version" => version,
|
177
|
+
"changelog" => changelog,
|
178
|
+
},
|
179
|
+
}.to_json
|
180
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
181
|
+
p http.post(uri.path, req, {'Content-Type'=>'application/json'})
|
182
|
+
end
|
12
183
|
end
|
data/bin/local-openid
CHANGED
@@ -1,300 +1,19 @@
|
|
1
|
-
#!/
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
require 'time'
|
9
|
-
require 'yaml'
|
10
|
-
|
11
|
-
require 'sinatra'
|
12
|
-
require 'openid'
|
13
|
-
require 'openid/extensions/sreg'
|
14
|
-
require 'openid/extensions/pape'
|
15
|
-
require 'openid/store/filesystem'
|
16
|
-
set :static, false
|
17
|
-
set :sessions, true
|
18
|
-
set :environment, :production
|
19
|
-
set :logging, false # load Rack::CommonLogger in config.ru instead
|
20
|
-
|
21
|
-
BEGIN {
|
22
|
-
$local_openid ||=
|
23
|
-
File.expand_path(ENV['LOCAL_OPENID_DIR'] || '~/.local-openid')
|
24
|
-
Dir.mkdir($local_openid) unless File.directory?($local_openid)
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'local_openid'
|
3
|
+
require 'optparse'
|
4
|
+
require 'socket'
|
5
|
+
BasicSocket.do_not_reverse_lookup = true
|
6
|
+
opts = {
|
7
|
+
:server => 'webrick', # webrick is standard, and plenty fast enough
|
25
8
|
}
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# yes, I use gsub for templating because I find it easier than erb :P
|
35
|
-
PROMPT = %q!<html>
|
36
|
-
<head><title>OpenID login: %s</title></head>
|
37
|
-
<body><h1>reload this page when approved: %s</h1></body>
|
38
|
-
</html>!
|
39
|
-
|
40
|
-
XRDS_HTML = %q!<html><head>
|
41
|
-
<link rel="openid.server" href="%s" />
|
42
|
-
<link rel="openid2.provider" href="%s" />
|
43
|
-
<meta http-equiv="X-XRDS-Location" content="%sxrds" />
|
44
|
-
<title>OpenID server endpoint</title>
|
45
|
-
</head><body>OpenID server endpoint</body></html>!
|
46
|
-
|
47
|
-
XRDS_XML = %q!<?xml version="1.0" encoding="UTF-8"?>
|
48
|
-
<xrds:XRDS
|
49
|
-
xmlns:xrds="xri://$xrds"
|
50
|
-
xmlns="xri://$xrd*($v*2.0)">
|
51
|
-
<XRD>
|
52
|
-
<Service priority="0">
|
53
|
-
%types
|
54
|
-
<URI>%s</URI>
|
55
|
-
</Service>
|
56
|
-
</XRD>
|
57
|
-
</xrds:XRDS>!
|
58
|
-
|
59
|
-
CONFIG_HEADER = %!
|
60
|
-
This file may be changed by #{__FILE__} or your favorite $EDITOR
|
61
|
-
comments will be deleted when modified by #{__FILE__}. See the
|
62
|
-
comments end of this file for help on the format.
|
63
|
-
!.lstrip!
|
64
|
-
|
65
|
-
CONFIG_TRAILER = %!
|
66
|
-
Configuration file description.
|
67
|
-
|
68
|
-
* allowed_ips An array of strings representing IPs that may
|
69
|
-
authenticate through local-openid. Only put
|
70
|
-
IP addresses that you trust in here.
|
71
|
-
|
72
|
-
Each OpenID consumer trust root will have its own hash keyed by
|
73
|
-
the trust root URL. Keys in this hash are:
|
74
|
-
|
75
|
-
- expires The time at which this login will expire.
|
76
|
-
This is generally the only entry you need to edit
|
77
|
-
to approve a site. You may also delete this line
|
78
|
-
and rename the "expires1m" to this.
|
79
|
-
- expires1m The time 1 minute from when this entry was updated.
|
80
|
-
This is provided as a convenience for replacing
|
81
|
-
the default "expires" entry. This key may be safely
|
82
|
-
removed by a user editing it.
|
83
|
-
- updated Time this entry was updated, strictly informational.
|
84
|
-
- session_id Unique identifier in your session cookie to prevent
|
85
|
-
other users from hijacking your session. You may
|
86
|
-
delete this if you've changed browsers or computers.
|
87
|
-
- assoc_handle See the OpenID specs, may be empty. Do not edit this.
|
88
|
-
|
89
|
-
SReg keys supported by the Ruby OpenID implementation should be
|
90
|
-
supported, they include (but are not limited to):
|
91
|
-
! << OpenID::SReg::DATA_FIELDS.map do |key, value|
|
92
|
-
" - #{key}: #{value}"
|
93
|
-
end.join("\n") << %!
|
94
|
-
SReg keys may be global at the top-level or private to each trust root.
|
95
|
-
Per-trust root SReg entries override the global settings.
|
96
|
-
!
|
97
|
-
|
98
|
-
include OpenID::Server
|
99
|
-
|
100
|
-
# this is the heart of our provider logic, adapted from the
|
101
|
-
# Ruby OpenID gem Rails example
|
102
|
-
def get_or_post
|
103
|
-
oidreq = begin
|
104
|
-
server.decode_request(params)
|
105
|
-
rescue ProtocolError => err
|
106
|
-
halt(500, err.to_s)
|
107
|
-
end
|
108
|
-
|
109
|
-
oidreq or return render_xrds
|
110
|
-
|
111
|
-
oidresp = case oidreq
|
112
|
-
when CheckIDRequest
|
113
|
-
if oidreq.id_select && oidreq.immediate
|
114
|
-
oidreq.answer(false)
|
115
|
-
elsif is_authorized?(oidreq)
|
116
|
-
resp = oidreq.answer(true, nil, server_root)
|
117
|
-
add_sreg(oidreq, resp)
|
118
|
-
add_pape(oidreq, resp)
|
119
|
-
resp
|
120
|
-
elsif oidreq.immediate
|
121
|
-
oidreq.answer(false, server_root)
|
122
|
-
else
|
123
|
-
session[:id] ||= "#{Time.now.to_i}.#$$.#{rand}"
|
124
|
-
session[:ip] = request.ip
|
125
|
-
merge_config(oidreq)
|
126
|
-
write_config
|
127
|
-
|
128
|
-
# here we allow our user to open $EDITOR and edit the appropriate
|
129
|
-
# 'expires' field in config.yml corresponding to oidreq.trust_root
|
130
|
-
return PROMPT.gsub(/%s/, oidreq.trust_root)
|
131
|
-
end
|
132
|
-
else
|
133
|
-
server.handle_request(oidreq)
|
134
|
-
end
|
135
|
-
|
136
|
-
finalize_response(oidresp)
|
137
|
-
end
|
138
|
-
|
139
|
-
# we're the provider for exactly one identity. However, we do rely on
|
140
|
-
# being proxied and being hit with an appropriate HTTP Host: header.
|
141
|
-
# Don't expect OpenID consumers to handle port != 80.
|
142
|
-
def server_root
|
143
|
-
"http://#{request.host}/"
|
144
|
-
end
|
145
|
-
|
146
|
-
def server
|
147
|
-
@server ||= Server.new(
|
148
|
-
OpenID::Store::Filesystem.new("#$local_openid/store"),
|
149
|
-
server_root)
|
150
|
-
end
|
151
|
-
|
152
|
-
# support the simple registration extension if possible,
|
153
|
-
# allow per-site overrides of various data points
|
154
|
-
def add_sreg(oidreq, oidresp)
|
155
|
-
sregreq = OpenID::SReg::Request.from_openid_request(oidreq) or return
|
156
|
-
per_site = config[oidreq.trust_root] || {}
|
157
|
-
|
158
|
-
sreg_data = {}
|
159
|
-
sregreq.all_requested_fields.each do |field|
|
160
|
-
sreg_data[field] = per_site[field] || config[field]
|
161
|
-
end
|
162
|
-
|
163
|
-
sregresp = OpenID::SReg::Response.extract_response(sregreq, sreg_data)
|
164
|
-
oidresp.add_extension(sregresp)
|
165
|
-
end
|
166
|
-
|
167
|
-
def add_pape(oidreq, oidresp)
|
168
|
-
papereq = OpenID::PAPE::Request.from_openid_request(oidreq) or return
|
169
|
-
paperesp = OpenID::PAPE::Response.new(papereq.preferred_auth_policies,
|
170
|
-
papereq.max_auth_age)
|
171
|
-
# since this implementation requires shell/filesystem access to the
|
172
|
-
# OpenID server to authenticate, we can say we're at the highest
|
173
|
-
# auth level possible...
|
174
|
-
paperesp.add_policy_uri(OpenID::PAPE::AUTH_MULTI_FACTOR_PHYSICAL)
|
175
|
-
paperesp.auth_time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
176
|
-
paperesp.nist_auth_level = 4
|
177
|
-
oidresp.add_extension(paperesp)
|
178
|
-
end
|
179
|
-
|
180
|
-
def err(msg)
|
181
|
-
env['rack.errors'].write("#{msg}\n")
|
182
|
-
false
|
183
|
-
end
|
184
|
-
|
185
|
-
def finalize_response(oidresp)
|
186
|
-
server.signatory.sign(oidresp) if oidresp.needs_signing
|
187
|
-
web_response = server.encode_response(oidresp)
|
188
|
-
|
189
|
-
case web_response.code
|
190
|
-
when HTTP_OK
|
191
|
-
web_response.body
|
192
|
-
when HTTP_REDIRECT
|
193
|
-
location = web_response.headers['location']
|
194
|
-
err("redirecting to: #{location} ...")
|
195
|
-
redirect(location)
|
196
|
-
else
|
197
|
-
halt(500, web_response.body)
|
9
|
+
OptionParser.new { |op|
|
10
|
+
op.on('-s <mongrel|thin|webrick>') { |v| opts[:server] = v }
|
11
|
+
op.on('-p port') { |val| opts[:port] = val.to_i }
|
12
|
+
op.on('-o addr') { |val| opts[:bind] = val }
|
13
|
+
op.on('-h', '--help', 'Show this message') do
|
14
|
+
puts op.to_s
|
15
|
+
exit
|
198
16
|
end
|
199
|
-
|
200
|
-
|
201
|
-
# the heart of our custom authentication logic
|
202
|
-
def is_authorized?(oidreq)
|
203
|
-
(config['allowed_ips'] ||= []).include?(request.ip) or
|
204
|
-
return err("Not allowed: #{request.ip}\n" \
|
205
|
-
"You need to put this IP in the 'allowed_ips' array "\
|
206
|
-
"in:\n #$local_openid/config.yml")
|
207
|
-
|
208
|
-
request.ip == session[:ip] or
|
209
|
-
return err("session IP mismatch: " \
|
210
|
-
"#{request.ip.inspect} != #{session[:ip].inspect}")
|
211
|
-
|
212
|
-
trust_root = oidreq.trust_root
|
213
|
-
per_site = config[trust_root] or
|
214
|
-
return err("trust_root unknown: #{trust_root}")
|
215
|
-
|
216
|
-
session_id = session[:id] or return err("no session ID")
|
217
|
-
|
218
|
-
assoc_handle = per_site['assoc_handle'] # this may be nil
|
219
|
-
expires = per_site['expires'] or
|
220
|
-
return err("no expires (trust_root=#{trust_root})")
|
221
|
-
|
222
|
-
assoc_handle == oidreq.assoc_handle or
|
223
|
-
return err("assoc_handle mismatch: " \
|
224
|
-
"#{assoc_handle.inspect} != #{oidreq.assoc_handle.inspect}" \
|
225
|
-
" (trust_root=#{trust_root})")
|
17
|
+
}.parse!(ARGV)
|
226
18
|
|
227
|
-
|
228
|
-
return err("session ID mismatch: " \
|
229
|
-
"#{per_site['session_id'].inspect} != #{session_id.inspect}" \
|
230
|
-
" (trust_root=#{trust_root})")
|
231
|
-
|
232
|
-
expires > Time.now or
|
233
|
-
return err("Expired: #{expires.inspect} (trust_root=#{trust_root})")
|
234
|
-
|
235
|
-
true
|
236
|
-
end
|
237
|
-
|
238
|
-
def config
|
239
|
-
@config ||= begin
|
240
|
-
YAML.load(File.read("#$local_openid/config.yml"))
|
241
|
-
rescue Errno::ENOENT
|
242
|
-
{}
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def merge_config(oidreq)
|
247
|
-
per_site = config[oidreq.trust_root] ||= {}
|
248
|
-
per_site.merge!({
|
249
|
-
'assoc_handle' => oidreq.assoc_handle,
|
250
|
-
'expires' => Time.at(0).utc,
|
251
|
-
'updated' => Time.now.utc,
|
252
|
-
'expires1m' => Time.now.utc + 60, # easy edit to "expires" in $EDITOR
|
253
|
-
'session_id' => session[:id],
|
254
|
-
})
|
255
|
-
end
|
256
|
-
|
257
|
-
def write_config
|
258
|
-
path = "#$local_openid/config.yml"
|
259
|
-
tmp = Tempfile.new('config.yml', File.dirname(path))
|
260
|
-
tmp.syswrite(CONFIG_HEADER.gsub(/^/m, "# "))
|
261
|
-
tmp.syswrite(config.to_yaml)
|
262
|
-
tmp.syswrite(CONFIG_TRAILER.gsub(/^/m, "# "))
|
263
|
-
tmp.fsync
|
264
|
-
File.rename(tmp.path, path)
|
265
|
-
tmp.close!
|
266
|
-
end
|
267
|
-
|
268
|
-
# this output is designed to be parsed by OpenID consumers
|
269
|
-
def render_xrds(force = false)
|
270
|
-
if force || request.accept.include?('application/xrds+xml')
|
271
|
-
|
272
|
-
# this seems to work...
|
273
|
-
types = request.accept.include?('application/xrds+xml') ?
|
274
|
-
[ OpenID::OPENID_2_0_TYPE, OpenID::OPENID_1_0_TYPE, OpenID::SREG_URI ] :
|
275
|
-
[ OpenID::OPENID_IDP_2_0_TYPE ]
|
276
|
-
|
277
|
-
headers['Content-Type'] = 'application/xrds+xml'
|
278
|
-
types = types.map { |uri| "<Type>#{uri}</Type>" }.join("\n")
|
279
|
-
XRDS_XML.gsub(/%s/, server_root).gsub!(/%types/, types)
|
280
|
-
else # render a browser-friendly page with an XRDS pointer
|
281
|
-
headers['X-XRDS-Location'] = "#{server_root}xrds"
|
282
|
-
XRDS_HTML.gsub(/%s/, server_root)
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
# if a single-user OpenID provider like us is being hit by multiple
|
287
|
-
# clients at once, then something is seriously wrong. Can't use
|
288
|
-
# Mutexes here since somebody could be running this as a CGI script
|
289
|
-
def big_lock(&block)
|
290
|
-
lock = "#$local_openid/lock"
|
291
|
-
File.open(lock, File::WRONLY|File::CREAT|File::EXCL, 0600) do |fp|
|
292
|
-
begin
|
293
|
-
yield
|
294
|
-
ensure
|
295
|
-
File.unlink(lock)
|
296
|
-
end
|
297
|
-
end
|
298
|
-
rescue Errno::EEXIST
|
299
|
-
err("Lock: #{lock} exists! Possible hijacking attempt") rescue nil
|
300
|
-
end
|
19
|
+
LocalOpenID.run!(opts)
|
data/lib/local_openid.rb
ADDED
@@ -0,0 +1,302 @@
|
|
1
|
+
# A personal OpenID identity provider, authentication is done by editing
|
2
|
+
# a YAML file on the server where this application runs
|
3
|
+
# (~/.local-openid/config.yml by default) instead of via HTTP/HTTPS
|
4
|
+
# form authentication in the browser.
|
5
|
+
#:stopdoc:
|
6
|
+
require 'tempfile'
|
7
|
+
require 'time'
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
require 'sinatra/base'
|
11
|
+
require 'openid'
|
12
|
+
require 'openid/extensions/sreg'
|
13
|
+
require 'openid/extensions/pape'
|
14
|
+
require 'openid/store/filesystem'
|
15
|
+
|
16
|
+
class LocalOpenID < Sinatra::Base
|
17
|
+
set :static, false
|
18
|
+
set :sessions, true
|
19
|
+
set :environment, :production
|
20
|
+
set :logging, false # load Rack::CommonLogger in config.ru instead
|
21
|
+
|
22
|
+
@@dir ||= File.expand_path(ENV['LOCAL_OPENID_DIR'] || '~/.local-openid')
|
23
|
+
Dir.mkdir(@@dir) unless File.directory?(@@dir)
|
24
|
+
|
25
|
+
# all the sinatra endpoints:
|
26
|
+
get('/xrds') { big_lock { render_xrds(true) } }
|
27
|
+
get('/') { big_lock { get_or_post } }
|
28
|
+
post('/') { big_lock { get_or_post } }
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# yes, I use gsub for templating because I find it easier than erb :P
|
33
|
+
PROMPT = %q!<html>
|
34
|
+
<head><title>OpenID login: %s</title></head>
|
35
|
+
<body><h1>reload this page when approved: %s</h1></body>
|
36
|
+
</html>!
|
37
|
+
|
38
|
+
XRDS_HTML = %q!<html><head>
|
39
|
+
<link rel="openid.server" href="%s" />
|
40
|
+
<link rel="openid2.provider" href="%s" />
|
41
|
+
<meta http-equiv="X-XRDS-Location" content="%sxrds" />
|
42
|
+
<title>OpenID server endpoint</title>
|
43
|
+
</head><body>OpenID server endpoint</body></html>!
|
44
|
+
|
45
|
+
XRDS_XML = %q!<?xml version="1.0" encoding="UTF-8"?>
|
46
|
+
<xrds:XRDS
|
47
|
+
xmlns:xrds="xri://$xrds"
|
48
|
+
xmlns="xri://$xrd*($v*2.0)">
|
49
|
+
<XRD>
|
50
|
+
<Service priority="0">
|
51
|
+
%types
|
52
|
+
<URI>%s</URI>
|
53
|
+
</Service>
|
54
|
+
</XRD>
|
55
|
+
</xrds:XRDS>!
|
56
|
+
|
57
|
+
CONFIG_HEADER = %!
|
58
|
+
This file may be changed by #{__FILE__} or your favorite $EDITOR
|
59
|
+
comments will be deleted when modified by #{__FILE__}. See the
|
60
|
+
comments end of this file for help on the format.
|
61
|
+
!.lstrip!
|
62
|
+
|
63
|
+
CONFIG_TRAILER = %!
|
64
|
+
Configuration file description.
|
65
|
+
|
66
|
+
* allowed_ips An array of strings representing IPs that may
|
67
|
+
authenticate through local-openid. Only put
|
68
|
+
IP addresses that you trust in here.
|
69
|
+
|
70
|
+
Each OpenID consumer trust root will have its own hash keyed by
|
71
|
+
the trust root URL. Keys in this hash are:
|
72
|
+
|
73
|
+
- expires The time at which this login will expire.
|
74
|
+
This is generally the only entry you need to edit
|
75
|
+
to approve a site. You may also delete this line
|
76
|
+
and rename the "expires1m" to this.
|
77
|
+
- expires1m The time 1 minute from when this entry was updated.
|
78
|
+
This is provided as a convenience for replacing
|
79
|
+
the default "expires" entry. This key may be safely
|
80
|
+
removed by a user editing it.
|
81
|
+
- updated Time this entry was updated, strictly informational.
|
82
|
+
- session_id Unique identifier in your session cookie to prevent
|
83
|
+
other users from hijacking your session. You may
|
84
|
+
delete this if you've changed browsers or computers.
|
85
|
+
- assoc_handle See the OpenID specs, may be empty. Do not edit this.
|
86
|
+
|
87
|
+
SReg keys supported by the Ruby OpenID implementation should be
|
88
|
+
supported, they include (but are not limited to):
|
89
|
+
! << OpenID::SReg::DATA_FIELDS.map do |key, value|
|
90
|
+
" - #{key}: #{value}"
|
91
|
+
end.join("\n") << %!
|
92
|
+
SReg keys may be global at the top-level or private to each trust root.
|
93
|
+
Per-trust root SReg entries override the global settings.
|
94
|
+
!
|
95
|
+
|
96
|
+
include OpenID::Server
|
97
|
+
|
98
|
+
# this is the heart of our provider logic, adapted from the
|
99
|
+
# Ruby OpenID gem Rails example
|
100
|
+
def get_or_post
|
101
|
+
oidreq = begin
|
102
|
+
server.decode_request(params)
|
103
|
+
rescue ProtocolError => err
|
104
|
+
halt(500, err.to_s)
|
105
|
+
end
|
106
|
+
|
107
|
+
oidreq or return render_xrds
|
108
|
+
|
109
|
+
oidresp = case oidreq
|
110
|
+
when CheckIDRequest
|
111
|
+
if oidreq.id_select && oidreq.immediate
|
112
|
+
oidreq.answer(false)
|
113
|
+
elsif is_authorized?(oidreq)
|
114
|
+
resp = oidreq.answer(true, nil, server_root)
|
115
|
+
add_sreg(oidreq, resp)
|
116
|
+
add_pape(oidreq, resp)
|
117
|
+
resp
|
118
|
+
elsif oidreq.immediate
|
119
|
+
oidreq.answer(false, server_root)
|
120
|
+
else
|
121
|
+
session[:id] ||= "#{Time.now.to_i}.#$$.#{rand}"
|
122
|
+
session[:ip] = request.ip
|
123
|
+
merge_config(oidreq)
|
124
|
+
write_config
|
125
|
+
|
126
|
+
# here we allow our user to open $EDITOR and edit the appropriate
|
127
|
+
# 'expires' field in config.yml corresponding to oidreq.trust_root
|
128
|
+
return PROMPT.gsub(/%s/, oidreq.trust_root)
|
129
|
+
end
|
130
|
+
else
|
131
|
+
server.handle_request(oidreq)
|
132
|
+
end
|
133
|
+
|
134
|
+
finalize_response(oidresp)
|
135
|
+
end
|
136
|
+
|
137
|
+
# we're the provider for exactly one identity. However, we do rely on
|
138
|
+
# being proxied and being hit with an appropriate HTTP Host: header.
|
139
|
+
# Don't expect OpenID consumers to handle port != 80.
|
140
|
+
def server_root
|
141
|
+
"http://#{request.host}/"
|
142
|
+
end
|
143
|
+
|
144
|
+
def server
|
145
|
+
@server ||= Server.new(
|
146
|
+
OpenID::Store::Filesystem.new("#@@dir/store"),
|
147
|
+
server_root)
|
148
|
+
end
|
149
|
+
|
150
|
+
# support the simple registration extension if possible,
|
151
|
+
# allow per-site overrides of various data points
|
152
|
+
def add_sreg(oidreq, oidresp)
|
153
|
+
sregreq = OpenID::SReg::Request.from_openid_request(oidreq) or return
|
154
|
+
per_site = config[oidreq.trust_root] || {}
|
155
|
+
|
156
|
+
sreg_data = {}
|
157
|
+
sregreq.all_requested_fields.each do |field|
|
158
|
+
sreg_data[field] = per_site[field] || config[field]
|
159
|
+
end
|
160
|
+
|
161
|
+
sregresp = OpenID::SReg::Response.extract_response(sregreq, sreg_data)
|
162
|
+
oidresp.add_extension(sregresp)
|
163
|
+
end
|
164
|
+
|
165
|
+
def add_pape(oidreq, oidresp)
|
166
|
+
papereq = OpenID::PAPE::Request.from_openid_request(oidreq) or return
|
167
|
+
paperesp = OpenID::PAPE::Response.new(papereq.preferred_auth_policies,
|
168
|
+
papereq.max_auth_age)
|
169
|
+
# since this implementation requires shell/filesystem access to the
|
170
|
+
# OpenID server to authenticate, we can say we're at the highest
|
171
|
+
# auth level possible...
|
172
|
+
paperesp.add_policy_uri(OpenID::PAPE::AUTH_MULTI_FACTOR_PHYSICAL)
|
173
|
+
paperesp.auth_time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
174
|
+
paperesp.nist_auth_level = 4
|
175
|
+
oidresp.add_extension(paperesp)
|
176
|
+
end
|
177
|
+
|
178
|
+
def err(msg)
|
179
|
+
env['rack.errors'].write("#{msg}\n")
|
180
|
+
false
|
181
|
+
end
|
182
|
+
|
183
|
+
def finalize_response(oidresp)
|
184
|
+
server.signatory.sign(oidresp) if oidresp.needs_signing
|
185
|
+
web_response = server.encode_response(oidresp)
|
186
|
+
|
187
|
+
case web_response.code
|
188
|
+
when HTTP_OK
|
189
|
+
web_response.body
|
190
|
+
when HTTP_REDIRECT
|
191
|
+
location = web_response.headers['location']
|
192
|
+
err("redirecting to: #{location} ...")
|
193
|
+
redirect(location)
|
194
|
+
else
|
195
|
+
halt(500, web_response.body)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# the heart of our custom authentication logic
|
200
|
+
def is_authorized?(oidreq)
|
201
|
+
(config['allowed_ips'] ||= []).include?(request.ip) or
|
202
|
+
return err("Not allowed: #{request.ip}\n" \
|
203
|
+
"You need to put this IP in the 'allowed_ips' array "\
|
204
|
+
"in:\n #@@dir/config.yml")
|
205
|
+
|
206
|
+
request.ip == session[:ip] or
|
207
|
+
return err("session IP mismatch: " \
|
208
|
+
"#{request.ip.inspect} != #{session[:ip].inspect}")
|
209
|
+
|
210
|
+
trust_root = oidreq.trust_root
|
211
|
+
per_site = config[trust_root] or
|
212
|
+
return err("trust_root unknown: #{trust_root}")
|
213
|
+
|
214
|
+
session_id = session[:id] or return err("no session ID")
|
215
|
+
|
216
|
+
assoc_handle = per_site['assoc_handle'] # this may be nil
|
217
|
+
expires = per_site['expires'] or
|
218
|
+
return err("no expires (trust_root=#{trust_root})")
|
219
|
+
|
220
|
+
assoc_handle == oidreq.assoc_handle or
|
221
|
+
return err("assoc_handle mismatch: " \
|
222
|
+
"#{assoc_handle.inspect} != #{oidreq.assoc_handle.inspect}" \
|
223
|
+
" (trust_root=#{trust_root})")
|
224
|
+
|
225
|
+
per_site['session_id'] == session_id or
|
226
|
+
return err("session ID mismatch: " \
|
227
|
+
"#{per_site['session_id'].inspect} != #{session_id.inspect}" \
|
228
|
+
" (trust_root=#{trust_root})")
|
229
|
+
|
230
|
+
expires > Time.now or
|
231
|
+
return err("Expired: #{expires.inspect} (trust_root=#{trust_root})")
|
232
|
+
|
233
|
+
true
|
234
|
+
end
|
235
|
+
|
236
|
+
def config
|
237
|
+
@config ||= begin
|
238
|
+
YAML.load(File.read("#@@dir/config.yml"))
|
239
|
+
rescue Errno::ENOENT
|
240
|
+
{}
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def merge_config(oidreq)
|
245
|
+
per_site = config[oidreq.trust_root] ||= {}
|
246
|
+
per_site.merge!({
|
247
|
+
'assoc_handle' => oidreq.assoc_handle,
|
248
|
+
'expires' => Time.at(0).utc,
|
249
|
+
'updated' => Time.now.utc,
|
250
|
+
'expires1m' => Time.now.utc + 60, # easy edit to "expires" in $EDITOR
|
251
|
+
'session_id' => session[:id],
|
252
|
+
})
|
253
|
+
end
|
254
|
+
|
255
|
+
def write_config
|
256
|
+
path = "#@@dir/config.yml"
|
257
|
+
tmp = Tempfile.new('config.yml', File.dirname(path))
|
258
|
+
tmp.syswrite(CONFIG_HEADER.gsub(/^/m, "# "))
|
259
|
+
tmp.syswrite(config.to_yaml)
|
260
|
+
tmp.syswrite(CONFIG_TRAILER.gsub(/^/m, "# "))
|
261
|
+
tmp.fsync
|
262
|
+
File.rename(tmp.path, path)
|
263
|
+
tmp.close!
|
264
|
+
end
|
265
|
+
|
266
|
+
# this output is designed to be parsed by OpenID consumers
|
267
|
+
def render_xrds(force = false)
|
268
|
+
if force || request.accept.include?('application/xrds+xml')
|
269
|
+
|
270
|
+
# this seems to work...
|
271
|
+
types = request.accept.include?('application/xrds+xml') ?
|
272
|
+
[ OpenID::OPENID_2_0_TYPE,
|
273
|
+
OpenID::OPENID_1_0_TYPE,
|
274
|
+
OpenID::SREG_URI ] :
|
275
|
+
[ OpenID::OPENID_IDP_2_0_TYPE ]
|
276
|
+
|
277
|
+
headers['Content-Type'] = 'application/xrds+xml'
|
278
|
+
types = types.map { |uri| "<Type>#{uri}</Type>" }.join("\n")
|
279
|
+
XRDS_XML.gsub(/%s/, server_root).gsub!(/%types/, types)
|
280
|
+
else # render a browser-friendly page with an XRDS pointer
|
281
|
+
headers['X-XRDS-Location'] = "#{server_root}xrds"
|
282
|
+
XRDS_HTML.gsub(/%s/, server_root)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# if a single-user OpenID provider like us is being hit by multiple
|
287
|
+
# clients at once, then something is seriously wrong. Can't use
|
288
|
+
# Mutexes here since somebody could be running this as a CGI script
|
289
|
+
def big_lock(&block)
|
290
|
+
lock = "#@@dir/lock"
|
291
|
+
File.open(lock, File::WRONLY|File::CREAT|File::EXCL, 0600) do |fp|
|
292
|
+
begin
|
293
|
+
yield
|
294
|
+
ensure
|
295
|
+
File.unlink(lock)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
rescue Errno::EEXIST
|
299
|
+
err("Lock: #{lock} exists! Possible hijacking attempt") rescue nil
|
300
|
+
end
|
301
|
+
end
|
302
|
+
#:startdoc:
|
@@ -0,0 +1,34 @@
|
|
1
|
+
ENV["VERSION"] or abort "VERSION= must be specified"
|
2
|
+
manifest = File.readlines('.manifest').map! { |x| x.chomp! }
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = %q{local-openid}
|
6
|
+
s.version = ENV["VERSION"]
|
7
|
+
|
8
|
+
s.authors = ["Eric Wong"]
|
9
|
+
s.date = Time.now.utc.strftime('%Y-%m-%d')
|
10
|
+
s.description = File.read("README").split(/\n\n/)[1]
|
11
|
+
s.email = %q{local-openid@librelist.com}
|
12
|
+
s.executables = %w(local-openid)
|
13
|
+
|
14
|
+
s.extra_rdoc_files = File.readlines('.document').map! do |x|
|
15
|
+
x.chomp!
|
16
|
+
if File.directory?(x)
|
17
|
+
manifest.grep(%r{\A#{x}/})
|
18
|
+
elsif File.file?(x)
|
19
|
+
x
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end.flatten.compact
|
24
|
+
|
25
|
+
s.files = manifest
|
26
|
+
s.homepage = %q{http://bogomips.org/local-openid/}
|
27
|
+
s.summary = %q{Single User, Ephemeral OpenID Provider}
|
28
|
+
s.rdoc_options = [ "-a", "-t", "local-openid - #{s.summary}" ]
|
29
|
+
s.require_paths = %w(lib)
|
30
|
+
s.rubyforge_project = %q{qrp}
|
31
|
+
s.add_dependency(%q<sinatra>, ["~> 1.0.0"])
|
32
|
+
s.add_dependency(%q<ruby-openid>, ["~> 2.1.7"])
|
33
|
+
# s.licenses = %w(AGPLv3) # accessor not compatible with older RubyGems
|
34
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: local-openid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Eric Wong
|
@@ -9,78 +15,107 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date:
|
18
|
+
date: 2010-06-26 00:00:00 +00:00
|
13
19
|
default_executable:
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
22
|
name: sinatra
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
20
26
|
requirements:
|
21
|
-
- -
|
27
|
+
- - ~>
|
22
28
|
- !ruby/object:Gem::Version
|
23
|
-
|
24
|
-
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 1.0.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
25
37
|
- !ruby/object:Gem::Dependency
|
26
|
-
name:
|
27
|
-
|
28
|
-
|
29
|
-
|
38
|
+
name: ruby-openid
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
30
42
|
requirements:
|
31
|
-
- -
|
43
|
+
- - ~>
|
32
44
|
- !ruby/object:Gem::Version
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
-
|
45
|
+
hash: 5
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 1
|
49
|
+
- 7
|
50
|
+
version: 2.1.7
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
description: |-
|
54
|
+
local-openid allows users with shell accounts on servers to authenticate
|
55
|
+
with OpenID consumers by editing a YAML file in their home directory
|
56
|
+
instead of authenticating through HTTP/HTTPS.
|
57
|
+
email: local-openid@librelist.com
|
38
58
|
executables:
|
39
59
|
- local-openid
|
40
60
|
extensions: []
|
41
61
|
|
42
62
|
extra_rdoc_files:
|
43
|
-
-
|
44
|
-
- LICENSE
|
45
|
-
-
|
46
|
-
- README
|
63
|
+
- NEWS
|
64
|
+
- LICENSE
|
65
|
+
- ChangeLog
|
66
|
+
- README
|
47
67
|
files:
|
48
68
|
- .document
|
49
69
|
- .gitignore
|
70
|
+
- .manifest
|
71
|
+
- COPYING
|
72
|
+
- ChangeLog
|
73
|
+
- GIT-VERSION-FILE
|
74
|
+
- GIT-VERSION-GEN
|
50
75
|
- GNUmakefile
|
51
|
-
-
|
52
|
-
-
|
53
|
-
-
|
54
|
-
- README.txt
|
76
|
+
- LICENSE
|
77
|
+
- NEWS
|
78
|
+
- README
|
55
79
|
- Rakefile
|
56
80
|
- bin/local-openid
|
81
|
+
- lib/local_openid.rb
|
82
|
+
- local-openid.gemspec
|
57
83
|
- setup.rb
|
58
84
|
has_rdoc: true
|
59
|
-
homepage: http://bogomips.org/local-openid
|
85
|
+
homepage: http://bogomips.org/local-openid/
|
86
|
+
licenses: []
|
87
|
+
|
60
88
|
post_install_message:
|
61
89
|
rdoc_options:
|
62
|
-
-
|
63
|
-
-
|
90
|
+
- -a
|
91
|
+
- -t
|
92
|
+
- local-openid - Single User, Ephemeral OpenID Provider
|
64
93
|
require_paths:
|
65
94
|
- lib
|
66
95
|
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
67
97
|
requirements:
|
68
98
|
- - ">="
|
69
99
|
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
70
103
|
version: "0"
|
71
|
-
version:
|
72
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
73
106
|
requirements:
|
74
107
|
- - ">="
|
75
108
|
- !ruby/object:Gem::Version
|
109
|
+
hash: 3
|
110
|
+
segments:
|
111
|
+
- 0
|
76
112
|
version: "0"
|
77
|
-
version:
|
78
113
|
requirements: []
|
79
114
|
|
80
115
|
rubyforge_project: qrp
|
81
|
-
rubygems_version: 1.3.
|
116
|
+
rubygems_version: 1.3.7
|
82
117
|
signing_key:
|
83
|
-
specification_version:
|
118
|
+
specification_version: 3
|
84
119
|
summary: Single User, Ephemeral OpenID Provider
|
85
120
|
test_files: []
|
86
121
|
|
data/History.txt
DELETED