local-openid 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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