cortex-reaver 0.0.1
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/LICENSE +25 -0
- data/README +48 -0
- data/bin/console +11 -0
- data/bin/cortex_reaver +110 -0
- data/bin/import.rb +78 -0
- data/lib/cortex_reaver.rb +114 -0
- data/lib/cortex_reaver/config.rb +21 -0
- data/lib/cortex_reaver/controller/comment.rb +113 -0
- data/lib/cortex_reaver/controller/config.rb +14 -0
- data/lib/cortex_reaver/controller/journal.rb +40 -0
- data/lib/cortex_reaver/controller/main.rb +56 -0
- data/lib/cortex_reaver/controller/page.rb +34 -0
- data/lib/cortex_reaver/controller/photograph.rb +45 -0
- data/lib/cortex_reaver/controller/project.rb +40 -0
- data/lib/cortex_reaver/controller/tag.rb +67 -0
- data/lib/cortex_reaver/controller/user.rb +71 -0
- data/lib/cortex_reaver/helper/activity.rb +9 -0
- data/lib/cortex_reaver/helper/attachments.rb +139 -0
- data/lib/cortex_reaver/helper/auth.rb +45 -0
- data/lib/cortex_reaver/helper/canonical.rb +55 -0
- data/lib/cortex_reaver/helper/crud.rb +317 -0
- data/lib/cortex_reaver/helper/date.rb +29 -0
- data/lib/cortex_reaver/helper/error.rb +14 -0
- data/lib/cortex_reaver/helper/feeds.rb +88 -0
- data/lib/cortex_reaver/helper/form.rb +114 -0
- data/lib/cortex_reaver/helper/navigation.rb +142 -0
- data/lib/cortex_reaver/helper/photographs.rb +25 -0
- data/lib/cortex_reaver/helper/tags.rb +47 -0
- data/lib/cortex_reaver/helper/workflow.rb +27 -0
- data/lib/cortex_reaver/migrations/001_users.rb +24 -0
- data/lib/cortex_reaver/migrations/002_pages.rb +29 -0
- data/lib/cortex_reaver/migrations/003_journals.rb +26 -0
- data/lib/cortex_reaver/migrations/004_photographs.rb +24 -0
- data/lib/cortex_reaver/migrations/005_projects.rb +27 -0
- data/lib/cortex_reaver/migrations/006_tags.rb +64 -0
- data/lib/cortex_reaver/migrations/007_comments.rb +40 -0
- data/lib/cortex_reaver/migrations/008_config.rb +23 -0
- data/lib/cortex_reaver/model/comment.rb +109 -0
- data/lib/cortex_reaver/model/journal.rb +53 -0
- data/lib/cortex_reaver/model/page.rb +87 -0
- data/lib/cortex_reaver/model/photograph.rb +133 -0
- data/lib/cortex_reaver/model/project.rb +49 -0
- data/lib/cortex_reaver/model/tag.rb +72 -0
- data/lib/cortex_reaver/model/user.rb +147 -0
- data/lib/cortex_reaver/public/css/admin.css +45 -0
- data/lib/cortex_reaver/public/css/custom.css +0 -0
- data/lib/cortex_reaver/public/css/form.css +51 -0
- data/lib/cortex_reaver/public/css/main.css +325 -0
- data/lib/cortex_reaver/public/css/photo.css +113 -0
- data/lib/cortex_reaver/public/css/ramaze_error.css +90 -0
- data/lib/cortex_reaver/public/css/text.css +25 -0
- data/lib/cortex_reaver/public/dispatch.fcgi +11 -0
- data/lib/cortex_reaver/public/images/CortexReaver.gif +0 -0
- data/lib/cortex_reaver/public/images/atom-xml-icon.png +0 -0
- data/lib/cortex_reaver/public/images/body.png +0 -0
- data/lib/cortex_reaver/public/images/border_bottom.png +0 -0
- data/lib/cortex_reaver/public/images/border_bottom_left.png +0 -0
- data/lib/cortex_reaver/public/images/border_bottom_right.png +0 -0
- data/lib/cortex_reaver/public/images/border_left.png +0 -0
- data/lib/cortex_reaver/public/images/border_right.png +0 -0
- data/lib/cortex_reaver/public/images/border_top.png +0 -0
- data/lib/cortex_reaver/public/images/border_top_left.png +0 -0
- data/lib/cortex_reaver/public/images/border_top_right.png +0 -0
- data/lib/cortex_reaver/public/images/comment.gif +0 -0
- data/lib/cortex_reaver/public/images/dark_trans.png +0 -0
- data/lib/cortex_reaver/public/images/delete.gif +0 -0
- data/lib/cortex_reaver/public/images/edit.gif +0 -0
- data/lib/cortex_reaver/public/images/header.png +0 -0
- data/lib/cortex_reaver/public/images/header.xcf +0 -0
- data/lib/cortex_reaver/public/images/header_background.png +0 -0
- data/lib/cortex_reaver/public/images/parent.gif +0 -0
- data/lib/cortex_reaver/public/images/rss-xml-icon.png +0 -0
- data/lib/cortex_reaver/public/images/sections.png +0 -0
- data/lib/cortex_reaver/public/images/sections_highlight.png +0 -0
- data/lib/cortex_reaver/public/js/admin.js +36 -0
- data/lib/cortex_reaver/public/js/cookie.js +27 -0
- data/lib/cortex_reaver/public/js/jquery.js +32 -0
- data/lib/cortex_reaver/public/js/photo.js +33 -0
- data/lib/cortex_reaver/snippets/array.rb +7 -0
- data/lib/cortex_reaver/snippets/ramaze/dispatcher/file.rb +37 -0
- data/lib/cortex_reaver/support/attachments.rb +235 -0
- data/lib/cortex_reaver/support/cached_rendering.rb +79 -0
- data/lib/cortex_reaver/support/canonical.rb +107 -0
- data/lib/cortex_reaver/support/comments.rb +69 -0
- data/lib/cortex_reaver/support/pagination.rb +38 -0
- data/lib/cortex_reaver/support/renderer.rb +196 -0
- data/lib/cortex_reaver/support/sequenceable.rb +248 -0
- data/lib/cortex_reaver/support/tags.rb +108 -0
- data/lib/cortex_reaver/support/timestamps.rb +33 -0
- data/lib/cortex_reaver/version.rb +8 -0
- data/lib/cortex_reaver/view/adminbox.rhtml +56 -0
- data/lib/cortex_reaver/view/blank_layout.rhtml +46 -0
- data/lib/cortex_reaver/view/comments/comment.rhtml +34 -0
- data/lib/cortex_reaver/view/comments/form.rhtml +25 -0
- data/lib/cortex_reaver/view/comments/list.rhtml +5 -0
- data/lib/cortex_reaver/view/comments/post_form.rhtml +36 -0
- data/lib/cortex_reaver/view/config/form.rhtml +10 -0
- data/lib/cortex_reaver/view/error.rhtml +72 -0
- data/lib/cortex_reaver/view/journals/form.rhtml +12 -0
- data/lib/cortex_reaver/view/journals/journal.rhtml +39 -0
- data/lib/cortex_reaver/view/journals/list.rhtml +33 -0
- data/lib/cortex_reaver/view/journals/short.rhtml +3 -0
- data/lib/cortex_reaver/view/journals/show.rhtml +5 -0
- data/lib/cortex_reaver/view/pages/form.rhtml +12 -0
- data/lib/cortex_reaver/view/pages/list.rhtml +26 -0
- data/lib/cortex_reaver/view/pages/show.rhtml +12 -0
- data/lib/cortex_reaver/view/photographs/atom_fragment.rhtml +82 -0
- data/lib/cortex_reaver/view/photographs/form.rhtml +19 -0
- data/lib/cortex_reaver/view/photographs/grid.rhtml +36 -0
- data/lib/cortex_reaver/view/photographs/list.rhtml +9 -0
- data/lib/cortex_reaver/view/photographs/short.rhtml +3 -0
- data/lib/cortex_reaver/view/photographs/show.rhtml +114 -0
- data/lib/cortex_reaver/view/photographs/sidebar.rhtml +7 -0
- data/lib/cortex_reaver/view/projects/form.rhtml +13 -0
- data/lib/cortex_reaver/view/projects/list.rhtml +27 -0
- data/lib/cortex_reaver/view/projects/show.rhtml +38 -0
- data/lib/cortex_reaver/view/tags/form.rhtml +9 -0
- data/lib/cortex_reaver/view/tags/list.rhtml +28 -0
- data/lib/cortex_reaver/view/tags/show.rhtml +51 -0
- data/lib/cortex_reaver/view/text_layout.rhtml +78 -0
- data/lib/cortex_reaver/view/users/form.rhtml +16 -0
- data/lib/cortex_reaver/view/users/list.rhtml +38 -0
- data/lib/cortex_reaver/view/users/login.rhtml +13 -0
- data/lib/cortex_reaver/view/users/register.rhtml +13 -0
- data/lib/cortex_reaver/view/users/show.rhtml +37 -0
- data/lib/cortex_reaver/view/users/user.rhtml +35 -0
- data/lib/proto/cortex_reaver.yaml +28 -0
- metadata +285 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
module CortexReaver
|
|
2
|
+
class Photograph < Sequel::Model(:photographs)
|
|
3
|
+
def self.url
|
|
4
|
+
'/photographs'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
include CortexReaver::Model::Timestamps
|
|
8
|
+
include CortexReaver::Model::Canonical
|
|
9
|
+
include CortexReaver::Model::Attachments
|
|
10
|
+
include CortexReaver::Model::Comments
|
|
11
|
+
include CortexReaver::Model::Tags
|
|
12
|
+
include CortexReaver::Model::Sequenceable
|
|
13
|
+
|
|
14
|
+
# Target image sizes
|
|
15
|
+
SIZES = {
|
|
16
|
+
:thumbnail => '150x',
|
|
17
|
+
:grid => '150x150',
|
|
18
|
+
:small => 'x512',
|
|
19
|
+
:medium => 'x768',
|
|
20
|
+
:large => 'x1024',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
many_to_many :tags, :class => 'CortexReaver::Tag'
|
|
24
|
+
belongs_to :user, :class => 'CortexReaver::User'
|
|
25
|
+
has_many :comments, :class => 'CortexReaver::Comment'
|
|
26
|
+
|
|
27
|
+
validates do
|
|
28
|
+
uniqueness_of :name
|
|
29
|
+
presence_of :name
|
|
30
|
+
length_of :name, :maximum => 255
|
|
31
|
+
presence_of :title
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.get(id)
|
|
35
|
+
self[:name => id] || self[id]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.recent
|
|
39
|
+
reverse_order(:created_on).limit(16)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def atom_url
|
|
43
|
+
'/photographs/atom/' + name
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the exif data for this photograph
|
|
47
|
+
def exif
|
|
48
|
+
EXIFR::JPEG.new(self.full_local_path).exif
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Gives the best guess for this photograph's date.
|
|
52
|
+
def date
|
|
53
|
+
if exif = self.exif
|
|
54
|
+
fields = [:date_time_original, :date_time_digitized, :date_time]
|
|
55
|
+
fields.each do |field|
|
|
56
|
+
begin
|
|
57
|
+
if date = exif.send(field)
|
|
58
|
+
return date
|
|
59
|
+
end
|
|
60
|
+
rescue
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Fall back on creation date
|
|
66
|
+
self.created_on
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Store an photograph on disk in the appropriate sizes.
|
|
70
|
+
def image=(file)
|
|
71
|
+
if file.blank? or file.size == 0
|
|
72
|
+
return nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Write file to disk
|
|
76
|
+
attachment = Attachment.new(self, 'original.jpg')
|
|
77
|
+
attachment.file = file
|
|
78
|
+
|
|
79
|
+
# Read image through ImageMagick
|
|
80
|
+
image = Magick::Image.read(attachment.local_path).first
|
|
81
|
+
|
|
82
|
+
# Write appropriate sizes to disk
|
|
83
|
+
SIZES.each do |size, geometry|
|
|
84
|
+
image.change_geometry(geometry) do |width, height|
|
|
85
|
+
attachment = Attachment.new(self, size.to_s + '.jpg')
|
|
86
|
+
image.scale(width, height).write(attachment.local_path)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Sets the date from the EXIF date tag.
|
|
94
|
+
def infer_date_from_exif!
|
|
95
|
+
if exif = self.exif
|
|
96
|
+
self.created_on = self.date
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns the system path to the full photograph
|
|
101
|
+
def full_local_path
|
|
102
|
+
path :local, 'medium'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Returns the path to the full photograph
|
|
106
|
+
def full_public_path
|
|
107
|
+
path :public, 'medium'
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The path to a photograph
|
|
111
|
+
def path(type = :local, size = :full)
|
|
112
|
+
attachment(size.to_s + '.jpg').path(type)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns the system path to the thumbnail photograph
|
|
116
|
+
def thumbnail_local_path
|
|
117
|
+
path :local, 'thumbnail'
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Returns the path to the thumbnail photograph
|
|
121
|
+
def thumbnail_public_path
|
|
122
|
+
path :public, 'thumbnail'
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def to_s
|
|
126
|
+
title || name
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def url
|
|
130
|
+
'/photographs/show/' + name
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module CortexReaver
|
|
2
|
+
class Project < Sequel::Model(:projects)
|
|
3
|
+
def self.url
|
|
4
|
+
'/projects'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
include CortexReaver::Model::Timestamps
|
|
8
|
+
include CortexReaver::Model::CachedRendering
|
|
9
|
+
include CortexReaver::Model::Renderer
|
|
10
|
+
include CortexReaver::Model::Canonical
|
|
11
|
+
include CortexReaver::Model::Attachments
|
|
12
|
+
include CortexReaver::Model::Comments
|
|
13
|
+
include CortexReaver::Model::Tags
|
|
14
|
+
include CortexReaver::Model::Sequenceable
|
|
15
|
+
|
|
16
|
+
many_to_many :tags, :class => 'CortexReaver::Tag'
|
|
17
|
+
belongs_to :user, :class => 'CortexReaver::User'
|
|
18
|
+
has_many :comments, :class => 'CortexReaver::Comment'
|
|
19
|
+
|
|
20
|
+
validates do
|
|
21
|
+
uniqueness_of :name
|
|
22
|
+
presence_of :name
|
|
23
|
+
length_of :name, :maximum => 255
|
|
24
|
+
presence_of :title
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
render :body
|
|
28
|
+
|
|
29
|
+
def self.get(id)
|
|
30
|
+
self[:name => id] || self[id]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.recent
|
|
34
|
+
reverse_order(:updated_on).limit(16)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def atom_url
|
|
38
|
+
'/projects/atom/' + name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def url
|
|
42
|
+
'/projects/show/' + name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def to_s
|
|
46
|
+
title || name
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module CortexReaver
|
|
2
|
+
class Tag < Sequel::Model(:tags)
|
|
3
|
+
include CortexReaver::Model::Canonical
|
|
4
|
+
|
|
5
|
+
many_to_many :photographs, :class => 'CortexReaver::Photograph'
|
|
6
|
+
many_to_many :journals, :class => 'CortexReaver::Journal'
|
|
7
|
+
many_to_many :projects, :class => 'CortexReaver::Project'
|
|
8
|
+
many_to_many :pages, :class => 'CortexReaver::Page'
|
|
9
|
+
|
|
10
|
+
validates do
|
|
11
|
+
uniqueness_of :name
|
|
12
|
+
presence_of :name
|
|
13
|
+
length_of :name, :maximum => 255
|
|
14
|
+
presence_of :title
|
|
15
|
+
length_of :title, :maximum => 255
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# When we delete a tag, ensure nothing else is linked to it.
|
|
19
|
+
before_destroy(:drop_associations) do
|
|
20
|
+
remove_all_photographs
|
|
21
|
+
remove_all_journals
|
|
22
|
+
remove_all_projects
|
|
23
|
+
remove_all_pages
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Autocompletes a tag. Returns an array of matching candidates
|
|
27
|
+
def self.autocomplete(string)
|
|
28
|
+
filter(:title.like(/^#{string}/i)).limit(6).map(:title)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Recalculates the number of children on each tag, and saves the updated values.
|
|
32
|
+
def self.refresh_counts
|
|
33
|
+
all.each do |tag|
|
|
34
|
+
tag.refresh_count
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.get(id)
|
|
39
|
+
self[:name => id] || self[id]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.url
|
|
43
|
+
'/tags'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def atom_url
|
|
47
|
+
'/tags/atom/' + name
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Recalculates the number of children on this tag, and saves the update value.
|
|
51
|
+
def refresh_count
|
|
52
|
+
# Find counts
|
|
53
|
+
self[:count] = photographs_dataset.count +
|
|
54
|
+
journals_dataset.count +
|
|
55
|
+
pages_dataset.count +
|
|
56
|
+
projects_dataset.count
|
|
57
|
+
|
|
58
|
+
# Save and return
|
|
59
|
+
self.skip_timestamp_update = true
|
|
60
|
+
self.save
|
|
61
|
+
self[:count]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def url
|
|
65
|
+
'/tags/show/' + name
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_s
|
|
69
|
+
title || name
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
require 'digest/sha2'
|
|
2
|
+
|
|
3
|
+
module CortexReaver
|
|
4
|
+
class User < Sequel::Model(:users)
|
|
5
|
+
include CortexReaver::Model::Timestamps
|
|
6
|
+
include CortexReaver::Model::Sequenceable
|
|
7
|
+
|
|
8
|
+
has_many :comments, :class => 'CortexReaver::Comment'
|
|
9
|
+
has_many :journals, :class => 'CortexReaver::Journal'
|
|
10
|
+
has_many :pages, :class => 'CortexReaver::Page'
|
|
11
|
+
has_many :photographs, :class => 'CortexReaver::Photograph'
|
|
12
|
+
has_many :projects, :class => 'CortexReaver::Project'
|
|
13
|
+
|
|
14
|
+
validates do
|
|
15
|
+
uniqueness_of :login
|
|
16
|
+
length_of :login, :with => 5..255, :allow_blank => true
|
|
17
|
+
format_of :login, :with => /^[A-Za-z0-9\-_]+$/
|
|
18
|
+
length_of :name, :maximum => 255
|
|
19
|
+
length_of :http, :allow_blank => true, :maximum => 255
|
|
20
|
+
uniqueness_of :email
|
|
21
|
+
length_of :email, :allow_blank => true, :maximum => 255
|
|
22
|
+
format_of :email,
|
|
23
|
+
:with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/, :allow_blank => true
|
|
24
|
+
confirmation_of :password, :allow_nil => true#, :allow_false => true
|
|
25
|
+
each(:password_length, :tag => :password_length) do |object, attribute, value|
|
|
26
|
+
unless value.nil? or (8..255) === value
|
|
27
|
+
object.errors['password'] << 'must be between 8 and 255 characters.'
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Ensure an administrator is always available. TODO: This is probably
|
|
33
|
+
# subject to a race condition, especially between servers. Backup plan?
|
|
34
|
+
validates_each :admin do |object, attribute, value|
|
|
35
|
+
if User.filter(:admin => true).count == 1 and not value
|
|
36
|
+
object.errors[attribute] << "can't be unset; only one administrator left!"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
self.window_size = 64
|
|
41
|
+
|
|
42
|
+
# Returns an authenticated user by login and password, or nil.
|
|
43
|
+
def self.authenticate(login, password)
|
|
44
|
+
user = self[:login => login]
|
|
45
|
+
if user and user.authenticate(password)
|
|
46
|
+
user
|
|
47
|
+
else
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# CRUD uses this to construct URLs. Even though we don't need the full
|
|
53
|
+
# power of Canonical, CRUD is pretty useful. :)
|
|
54
|
+
def self.canonical_name_attr
|
|
55
|
+
:login
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get a user
|
|
59
|
+
def self.get(id)
|
|
60
|
+
self[:login => id] || self[id]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns true if the user is an administrator.
|
|
64
|
+
def admin?
|
|
65
|
+
self.admin
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Authenticate with password
|
|
69
|
+
def authenticate(test_password)
|
|
70
|
+
if self[:password] == self.class.crypt(test_password, self.salt)
|
|
71
|
+
true
|
|
72
|
+
else
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Set user password
|
|
78
|
+
def password=(password)
|
|
79
|
+
self.salt ||= self.class.new_salt
|
|
80
|
+
self[:password] = self.class.crypt(password, self.salt)
|
|
81
|
+
@password_length = password.length
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Password confirmation
|
|
85
|
+
def password_confirmation=(password)
|
|
86
|
+
self.salt ||= self.class.new_salt
|
|
87
|
+
@password_confirmation = self.class.crypt(password, self.salt)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def password_confirmation
|
|
91
|
+
# If password_confirmation was set, use that. Otherwise, fall back
|
|
92
|
+
# to the normal password, so we don't need set the confirmation every
|
|
93
|
+
# time the password is updated programmatically.
|
|
94
|
+
@password_confirmation || self.password
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# A cache for password length, so we can validate without keeping the
|
|
98
|
+
# password as plaintext.
|
|
99
|
+
def password_length
|
|
100
|
+
@password_length
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def to_s
|
|
104
|
+
if name.blank?
|
|
105
|
+
login
|
|
106
|
+
else
|
|
107
|
+
name
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# A URL to view this user
|
|
112
|
+
def url
|
|
113
|
+
'/users/show/' + login
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
# Valid characters for salt
|
|
118
|
+
SALT_CHARS = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a
|
|
119
|
+
|
|
120
|
+
# Returns hash of password with salt.
|
|
121
|
+
def self.crypt(password, salt)
|
|
122
|
+
Digest::SHA512.hexdigest(password.to_s + salt.to_s)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Returns random salt
|
|
126
|
+
def self.new_salt
|
|
127
|
+
salt = ''
|
|
128
|
+
40.times do
|
|
129
|
+
salt << SALT_CHARS[rand(SALT_CHARS.size - 1).to_i]
|
|
130
|
+
end
|
|
131
|
+
salt
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
public
|
|
135
|
+
|
|
136
|
+
# Create default user if none exist
|
|
137
|
+
if table_exists? and count == 0
|
|
138
|
+
u = User.new(
|
|
139
|
+
:login => 'shodan',
|
|
140
|
+
:name => 'Shodan',
|
|
141
|
+
:admin => true
|
|
142
|
+
)
|
|
143
|
+
u.password = 'shodan'
|
|
144
|
+
u.save!
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#adminbox {
|
|
2
|
+
display: none;
|
|
3
|
+
padding: 0.5em;
|
|
4
|
+
margin: 0 0 1em 0;
|
|
5
|
+
background-color: #eaeaea;
|
|
6
|
+
border: 1px solid #aaa;
|
|
7
|
+
border-top: none;
|
|
8
|
+
font-size: 80%;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
#main #adminbox {
|
|
12
|
+
margin: -0.8em 0em 1em 0em;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#adminbox .actions-table {
|
|
16
|
+
width: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#adminbox p {
|
|
20
|
+
margin: 0;
|
|
21
|
+
padding: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#adminbox td.title {
|
|
25
|
+
font-weight: bold;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#adminbox input[type=text] {
|
|
29
|
+
width: 160px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#admin_section_actions {
|
|
33
|
+
text-align: center;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#admin_page_actions {
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#admin_new_actions {
|
|
40
|
+
float: left;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#admin_user_actions {
|
|
44
|
+
float: right;
|
|
45
|
+
}
|