Major update that includes various breaking changes.
- backend is now a DODB::DataBase, not a passwd and group file anymore. - extras have been removed. A WIP User#profile field exists, that can be a JSON::Any. No profile validation has been implemented as of this commit. - authd now provides permission over resources, which is more precise than checking whether a user is part of a group. - permissions are now checked through authd once again: tokens don’t hold permissions anymore. - tokens are now minimal authentication “keys” to prove who you are and nothing more.ipc07
parent
6a947402d7
commit
b5c055b553
155
src/authd.cr
155
src/authd.cr
|
@ -6,6 +6,9 @@ require "ipc"
|
||||||
|
|
||||||
require "./user.cr"
|
require "./user.cr"
|
||||||
|
|
||||||
|
class AuthD::Exception < Exception
|
||||||
|
end
|
||||||
|
|
||||||
class AuthD::Response
|
class AuthD::Response
|
||||||
include JSON::Serializable
|
include JSON::Serializable
|
||||||
|
|
||||||
|
@ -49,13 +52,13 @@ class AuthD::Response
|
||||||
end
|
end
|
||||||
|
|
||||||
class User < Response
|
class User < Response
|
||||||
property user : Passwd::User
|
property user : ::AuthD::User::Public
|
||||||
|
|
||||||
initialize :user
|
initialize :user
|
||||||
end
|
end
|
||||||
|
|
||||||
class UserAdded < Response
|
class UserAdded < Response
|
||||||
property user : Passwd::User
|
property user : ::AuthD::User::Public
|
||||||
|
|
||||||
initialize :user
|
initialize :user
|
||||||
end
|
end
|
||||||
|
@ -66,28 +69,30 @@ class AuthD::Response
|
||||||
initialize :uid
|
initialize :uid
|
||||||
end
|
end
|
||||||
|
|
||||||
class Extra < Response
|
|
||||||
property user : Int32
|
|
||||||
property name : String
|
|
||||||
property extra : JSON::Any?
|
|
||||||
|
|
||||||
initialize :user, :name, :extra
|
|
||||||
end
|
|
||||||
|
|
||||||
class ExtraUpdated < Response
|
|
||||||
property user : Int32
|
|
||||||
property name : String
|
|
||||||
property extra : JSON::Any?
|
|
||||||
|
|
||||||
initialize :user, :name, :extra
|
|
||||||
end
|
|
||||||
|
|
||||||
class UsersList < Response
|
class UsersList < Response
|
||||||
property users : Array(Passwd::User)
|
property users : Array(::AuthD::User::Public)
|
||||||
|
|
||||||
initialize :users
|
initialize :users
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class PermissionCheck < Response
|
||||||
|
property user : Int32
|
||||||
|
property service : String
|
||||||
|
property resource : String
|
||||||
|
property permission : ::AuthD::User::PermissionLevel
|
||||||
|
|
||||||
|
initialize :service, :resource, :user, :permission
|
||||||
|
end
|
||||||
|
|
||||||
|
class PermissionSet < Response
|
||||||
|
property user : Int32
|
||||||
|
property service : String
|
||||||
|
property resource : String
|
||||||
|
property permission : ::AuthD::User::PermissionLevel
|
||||||
|
|
||||||
|
initialize :user, :service, :resource, :permission
|
||||||
|
end
|
||||||
|
|
||||||
# This creates a Request::Type enumeration. One entry for each request type.
|
# This creates a Request::Type enumeration. One entry for each request type.
|
||||||
{% begin %}
|
{% begin %}
|
||||||
enum Type
|
enum Type
|
||||||
|
@ -175,18 +180,15 @@ class AuthD::Request
|
||||||
|
|
||||||
property login : String
|
property login : String
|
||||||
property password : String
|
property password : String
|
||||||
property uid : Int32?
|
property profile : JSON::Any?
|
||||||
property gid : Int32?
|
|
||||||
property home : String?
|
|
||||||
property shell : String?
|
|
||||||
|
|
||||||
initialize :shared_key, :login, :password
|
initialize :shared_key, :login, :password, :profile
|
||||||
end
|
end
|
||||||
|
|
||||||
class GetUser < Request
|
class GetUser < Request
|
||||||
property uid : Int32
|
property user : Int32 | String
|
||||||
|
|
||||||
initialize :uid
|
initialize :user
|
||||||
end
|
end
|
||||||
|
|
||||||
class GetUserByCredentials < Request
|
class GetUserByCredentials < Request
|
||||||
|
@ -209,19 +211,9 @@ class AuthD::Request
|
||||||
class Request::Register < Request
|
class Request::Register < Request
|
||||||
property login : String
|
property login : String
|
||||||
property password : String
|
property password : String
|
||||||
|
property profile : JSON::Any?
|
||||||
|
|
||||||
initialize :login, :password
|
initialize :login, :password, :profile
|
||||||
end
|
|
||||||
|
|
||||||
class Request::GetExtra < Request
|
|
||||||
property token : String
|
|
||||||
property name : String
|
|
||||||
end
|
|
||||||
|
|
||||||
class Request::SetExtra < Request
|
|
||||||
property token : String
|
|
||||||
property name : String
|
|
||||||
property extra : JSON::Any
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class Request::UpdatePassword < Request
|
class Request::UpdatePassword < Request
|
||||||
|
@ -235,6 +227,29 @@ class AuthD::Request
|
||||||
property key : String?
|
property key : String?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class CheckPermission < Request
|
||||||
|
property shared_key : String
|
||||||
|
|
||||||
|
# FIXME: Make it Int32 | String
|
||||||
|
property user : Int32
|
||||||
|
property service : String
|
||||||
|
property resource : String
|
||||||
|
|
||||||
|
initialize :shared_key, :user, :service, :resource
|
||||||
|
end
|
||||||
|
|
||||||
|
class SetPermission < Request
|
||||||
|
property shared_key : String
|
||||||
|
|
||||||
|
# FIXME: Make it Int32 | String
|
||||||
|
property user : Int32
|
||||||
|
property service : String
|
||||||
|
property resource : String
|
||||||
|
property permission : ::AuthD::User::PermissionLevel
|
||||||
|
|
||||||
|
initialize :shared_key, :user, :service, :resource, :permission
|
||||||
|
end
|
||||||
|
|
||||||
# This creates a Request::Type enumeration. One entry for each request type.
|
# This creates a Request::Type enumeration. One entry for each request type.
|
||||||
{% begin %}
|
{% begin %}
|
||||||
enum Type
|
enum Type
|
||||||
|
@ -315,13 +330,13 @@ module AuthD
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_user?(uid : Int32)
|
def get_user?(uid_or_login : Int32 | String) : ::AuthD::User::Public?
|
||||||
send Request::GetUser.new uid
|
send Request::GetUser.new uid_or_login
|
||||||
|
|
||||||
response = read
|
response = Response.from_ipc read
|
||||||
|
|
||||||
if response.type == Response::Type::Ok.value.to_u8
|
if response.is_a? Response::User
|
||||||
User.from_json String.new response.payload
|
response.user
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
@ -334,14 +349,14 @@ module AuthD
|
||||||
def decode_token(token)
|
def decode_token(token)
|
||||||
user, meta = JWT.decode token, @key, JWT::Algorithm::HS256
|
user, meta = JWT.decode token, @key, JWT::Algorithm::HS256
|
||||||
|
|
||||||
user = Passwd::User.from_json user.to_json
|
user = ::AuthD::User::Public.from_json user.to_json
|
||||||
|
|
||||||
{user, meta}
|
{user, meta}
|
||||||
end
|
end
|
||||||
|
|
||||||
# FIXME: Extra options may be useful to implement here.
|
# FIXME: Extra options may be useful to implement here.
|
||||||
def add_user(login : String, password : String) : Passwd::User | Exception
|
def add_user(login : String, password : String, profile : JSON::Any?) : ::AuthD::User::Public | Exception
|
||||||
send Request::AddUser.new @key, login, password
|
send Request::AddUser.new @key, login, password, profile
|
||||||
|
|
||||||
response = Response.from_ipc read
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
@ -349,7 +364,7 @@ module AuthD
|
||||||
when Response::UserAdded
|
when Response::UserAdded
|
||||||
response.user
|
response.user
|
||||||
when Response::Error
|
when Response::Error
|
||||||
Exception.new response.reason
|
raise Exception.new response.reason
|
||||||
else
|
else
|
||||||
# Should not happen in serialized connections, but…
|
# Should not happen in serialized connections, but…
|
||||||
# it’ll happen if you run several requests at once.
|
# it’ll happen if you run several requests at once.
|
||||||
|
@ -357,6 +372,18 @@ module AuthD
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def register(login : String, password : String, profile : JSON::Any?) : ::AuthD::User::Public?
|
||||||
|
send Request::Register.new login, password, profile
|
||||||
|
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::UserAdded
|
||||||
|
when Response::Error
|
||||||
|
raise Exception.new response.reason
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def mod_user(uid : Int32, password : String? = nil, avatar : String? = nil) : Bool | Exception
|
def mod_user(uid : Int32, password : String? = nil, avatar : String? = nil) : Bool | Exception
|
||||||
request = Request::ModUser.new @key, uid
|
request = Request::ModUser.new @key, uid
|
||||||
|
|
||||||
|
@ -376,6 +403,40 @@ module AuthD
|
||||||
Exception.new "???"
|
Exception.new "???"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_permission(user : ::AuthD::User::Public, service_name : String, resource_name : String) : User::PermissionLevel
|
||||||
|
request = Request::CheckPermission.new @key, user.uid, service_name, resource_name
|
||||||
|
|
||||||
|
send request
|
||||||
|
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::PermissionCheck
|
||||||
|
response.permission
|
||||||
|
when Response
|
||||||
|
raise Exception.new "unexpected response: #{response.type}"
|
||||||
|
else
|
||||||
|
raise Exception.new "unexpected response"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_permission(uid : Int32, service : String, resource : String, permission : User::PermissionLevel)
|
||||||
|
request = Request::SetPermission.new @key, uid, service, resource, permission
|
||||||
|
|
||||||
|
send request
|
||||||
|
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::PermissionSet
|
||||||
|
true
|
||||||
|
when Response
|
||||||
|
raise Exception.new "unexpected response: #{response.type}"
|
||||||
|
else
|
||||||
|
raise Exception.new "unexpected response"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
223
src/main.cr
223
src/main.cr
|
@ -3,7 +3,6 @@ require "option_parser"
|
||||||
require "openssl"
|
require "openssl"
|
||||||
|
|
||||||
require "jwt"
|
require "jwt"
|
||||||
require "passwd"
|
|
||||||
require "ipc"
|
require "ipc"
|
||||||
require "dodb"
|
require "dodb"
|
||||||
|
|
||||||
|
@ -14,59 +13,114 @@ extend AuthD
|
||||||
class AuthD::Service
|
class AuthD::Service
|
||||||
property registrations_allowed = false
|
property registrations_allowed = false
|
||||||
|
|
||||||
def initialize(@passwd : Passwd, @jwt_key : String, @extras_root : String)
|
@users_per_login : DODB::Index(User)
|
||||||
|
|
||||||
|
def initialize(@storage_root : String, @jwt_key : String)
|
||||||
|
@users = DODB::DataBase(String, User).new @storage_root
|
||||||
|
@users_per_login = @users.new_index "login", &.login
|
||||||
|
|
||||||
|
@last_uid_file = "#{@storage_root}/last_used_uid"
|
||||||
|
end
|
||||||
|
|
||||||
|
def hash_password(password : String) : String
|
||||||
|
digest = OpenSSL::Digest.new "sha256"
|
||||||
|
digest << password
|
||||||
|
digest.hexdigest
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_uid
|
||||||
|
begin
|
||||||
|
uid = File.read(@last_uid_file).to_i
|
||||||
|
rescue
|
||||||
|
uid = 999
|
||||||
|
end
|
||||||
|
|
||||||
|
uid += 1
|
||||||
|
|
||||||
|
File.write @last_uid_file, uid.to_s
|
||||||
|
|
||||||
|
uid
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_request(request : AuthD::Request?, connection : IPC::Connection)
|
def handle_request(request : AuthD::Request?, connection : IPC::Connection)
|
||||||
case request
|
case request
|
||||||
when Request::GetToken
|
when Request::GetToken
|
||||||
user = @passwd.get_user request.login, request.password
|
user = @users_per_login.get request.login
|
||||||
|
|
||||||
|
if user.password_hash != hash_password request.password
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
if user.nil?
|
if user.nil?
|
||||||
return Response::Error.new "invalid credentials"
|
return Response::Error.new "invalid credentials"
|
||||||
end
|
end
|
||||||
|
|
||||||
token = JWT.encode user.to_h, @jwt_key, JWT::Algorithm::HS256
|
token = user.to_token
|
||||||
|
|
||||||
Response::Token.new token
|
Response::Token.new token.to_s @jwt_key
|
||||||
when Request::AddUser
|
when Request::AddUser
|
||||||
if request.shared_key != @jwt_key
|
if request.shared_key != @jwt_key
|
||||||
return Response::Error.new "invalid authentication key"
|
return Response::Error.new "invalid authentication key"
|
||||||
end
|
end
|
||||||
|
|
||||||
if @passwd.user_exists? request.login
|
if @users_per_login.get? request.login
|
||||||
return Response::Error.new "login already used"
|
return Response::Error.new "login already used"
|
||||||
end
|
end
|
||||||
|
|
||||||
user = @passwd.add_user request.login, request.password
|
password_hash = hash_password request.password
|
||||||
|
|
||||||
Response::UserAdded.new user
|
uid = new_uid
|
||||||
|
|
||||||
|
user = User.new uid, request.login, password_hash
|
||||||
|
|
||||||
|
request.profile.try do |profile|
|
||||||
|
user.profile = profile
|
||||||
|
end
|
||||||
|
|
||||||
|
@users[user.uid.to_s] = user
|
||||||
|
|
||||||
|
Response::UserAdded.new user.to_public
|
||||||
when Request::GetUserByCredentials
|
when Request::GetUserByCredentials
|
||||||
user = @passwd.get_user request.login, request.password
|
user = @users_per_login.get request.login
|
||||||
|
|
||||||
if user
|
unless user
|
||||||
Response::User.new user
|
return Response::Error.new "invalid credentials"
|
||||||
else
|
|
||||||
Response::Error.new "user not found"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if hash_password(request.password) != user.password_hash
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
Response::User.new user.to_public
|
||||||
when Request::GetUser
|
when Request::GetUser
|
||||||
user = @passwd.get_user request.uid
|
uid_or_login = request.user
|
||||||
|
user = if uid_or_login.is_a? Int32
|
||||||
if user
|
@users[uid_or_login.to_s]?
|
||||||
Response::User.new user
|
|
||||||
else
|
else
|
||||||
Response::Error.new "user not found"
|
@users_per_login.get? uid_or_login
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "user not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
Response::User.new user.to_public
|
||||||
when Request::ModUser
|
when Request::ModUser
|
||||||
if request.shared_key != @jwt_key
|
if request.shared_key != @jwt_key
|
||||||
return Response::Error.new "invalid authentication key"
|
return Response::Error.new "invalid authentication key"
|
||||||
end
|
end
|
||||||
|
|
||||||
password_hash = request.password.try do |s|
|
user = @users[request.uid.to_s]?
|
||||||
Passwd.hash_password s
|
|
||||||
|
unless user
|
||||||
|
return Response::Error.new "user not found"
|
||||||
end
|
end
|
||||||
|
|
||||||
@passwd.mod_user request.uid, password_hash: password_hash
|
password_hash = request.password.try do |s|
|
||||||
|
user.password_hash = hash_password s
|
||||||
|
end
|
||||||
|
|
||||||
|
@users[user.uid.to_s] = user
|
||||||
|
|
||||||
Response::UserEdited.new request.uid
|
Response::UserEdited.new request.uid
|
||||||
when Request::Register
|
when Request::Register
|
||||||
|
@ -74,48 +128,46 @@ class AuthD::Service
|
||||||
return Response::Error.new "registrations not allowed"
|
return Response::Error.new "registrations not allowed"
|
||||||
end
|
end
|
||||||
|
|
||||||
if @passwd.user_exists? request.login
|
if @users_per_login.get? request.login
|
||||||
return Response::Error.new "login already used"
|
return Response::Error.new "login already used"
|
||||||
end
|
end
|
||||||
|
|
||||||
user = @passwd.add_user request.login, request.password
|
uid = new_uid
|
||||||
|
password = hash_password request.password
|
||||||
|
|
||||||
Response::UserAdded.new user
|
user = User.new uid, request.login, password
|
||||||
when Request::GetExtra
|
|
||||||
user = get_user_from_token request.token
|
|
||||||
|
|
||||||
return Response::Error.new "invalid token" unless user
|
request.profile.try do |profile|
|
||||||
|
user.profile = profile
|
||||||
|
end
|
||||||
|
|
||||||
storage = DODB::DataBase(String, JSON::Any).new "#{@extras_root}/#{user.uid}"
|
@users[user.uid.to_s] = user
|
||||||
|
|
||||||
Response::Extra.new user.uid, request.name, storage[request.name]?
|
Response::UserAdded.new user.to_public
|
||||||
when Request::SetExtra
|
|
||||||
user = get_user_from_token request.token
|
|
||||||
|
|
||||||
return Response::Error.new "invalid token" unless user
|
|
||||||
|
|
||||||
storage = DODB::DataBase(String, JSON::Any).new "#{@extras_root}/#{user.uid}"
|
|
||||||
|
|
||||||
storage[request.name] = request.extra
|
|
||||||
|
|
||||||
Response::ExtraUpdated.new user.uid, request.name, request.extra
|
|
||||||
when Request::UpdatePassword
|
when Request::UpdatePassword
|
||||||
user = @passwd.get_user request.login, request.old_password
|
user = @users_per_login.get? request.login
|
||||||
|
|
||||||
return Response::Error.new "invalid credentials" unless user
|
unless user
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
password_hash = Passwd.hash_password request.new_password
|
if hash_password(request.old_password) != user.password_hash
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
@passwd.mod_user user.uid, password_hash: password_hash
|
user.password_hash = hash_password request.new_password
|
||||||
|
|
||||||
|
@users[user.uid.to_s] = user
|
||||||
|
|
||||||
Response::UserEdited.new user.uid
|
Response::UserEdited.new user.uid
|
||||||
when Request::ListUsers
|
when Request::ListUsers
|
||||||
|
# FIXME: Lines too long, repeatedly (>80c with 4c tabs).
|
||||||
request.token.try do |token|
|
request.token.try do |token|
|
||||||
user = get_user_from_token token
|
user = get_user_from_token token
|
||||||
|
|
||||||
return Response::Error.new "unauthorized (user not found from token)" unless user
|
return Response::Error.new "unauthorized (user not found from token)"
|
||||||
|
|
||||||
return Response::Error.new "unauthorized (user not in authd group)" unless user.groups.any? &.==("authd")
|
return Response::Error.new "unauthorized (user not in authd group)" unless user.permissions["authd"]?.try(&.["*"].>=(User::PermissionLevel::Read))
|
||||||
end
|
end
|
||||||
|
|
||||||
request.key.try do |key|
|
request.key.try do |key|
|
||||||
|
@ -124,16 +176,69 @@ class AuthD::Service
|
||||||
|
|
||||||
return Response::Error.new "unauthorized (no key nor token)" unless request.key || request.token
|
return Response::Error.new "unauthorized (no key nor token)" unless request.key || request.token
|
||||||
|
|
||||||
Response::UsersList.new @passwd.get_all_users
|
Response::UsersList.new @users.to_h.map &.[1].to_public
|
||||||
|
when Request::CheckPermission
|
||||||
|
unless request.shared_key == @jwt_key
|
||||||
|
return Response::Error.new "unauthorized"
|
||||||
|
end
|
||||||
|
|
||||||
|
user = @users[request.user.to_s]?
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "no such user"
|
||||||
|
end
|
||||||
|
|
||||||
|
service = request.service
|
||||||
|
service_permissions = user.permissions[service]?
|
||||||
|
|
||||||
|
if service_permissions.nil?
|
||||||
|
return Response::PermissionCheck.new service, request.resource, user.uid, User::PermissionLevel::None
|
||||||
|
end
|
||||||
|
|
||||||
|
resource_permissions = service_permissions[request.resource]?
|
||||||
|
|
||||||
|
if resource_permissions.nil?
|
||||||
|
return Response::PermissionCheck.new service, request.resource, user.uid, User::PermissionLevel::None
|
||||||
|
end
|
||||||
|
|
||||||
|
return Response::PermissionCheck.new service, request.resource, user.uid, resource_permissions
|
||||||
|
when Request::SetPermission
|
||||||
|
unless request.shared_key == @jwt_key
|
||||||
|
return Response::Error.new "unauthorized"
|
||||||
|
end
|
||||||
|
|
||||||
|
user = @users[request.user.to_s]?
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "no such user"
|
||||||
|
end
|
||||||
|
|
||||||
|
service = request.service
|
||||||
|
service_permissions = user.permissions[service]?
|
||||||
|
|
||||||
|
if service_permissions.nil?
|
||||||
|
service_permissions = Hash(String, User::PermissionLevel).new
|
||||||
|
user.permissions[service] = service_permissions
|
||||||
|
end
|
||||||
|
|
||||||
|
if request.permission.none?
|
||||||
|
service_permissions.delete request.resource
|
||||||
|
else
|
||||||
|
service_permissions[request.resource] = request.permission
|
||||||
|
end
|
||||||
|
|
||||||
|
@users[user.uid.to_s] = user
|
||||||
|
|
||||||
|
Response::PermissionSet.new user.uid, service, request.resource, request.permission
|
||||||
else
|
else
|
||||||
Response::Error.new "unhandled request type"
|
Response::Error.new "unhandled request type"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_user_from_token(token)
|
def get_user_from_token(token : String)
|
||||||
user, meta = JWT.decode token, @jwt_key, JWT::Algorithm::HS256
|
token_payload = Token.from_s(token, @jwt_key)
|
||||||
|
|
||||||
Passwd::User.from_json user.to_json
|
@users[token_payload.uid.to_s]?
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
|
@ -162,29 +267,19 @@ class AuthD::Service
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
authd_passwd_file = "passwd"
|
authd_storage = "storage"
|
||||||
authd_group_file = "group"
|
|
||||||
authd_jwt_key = "nico-nico-nii"
|
authd_jwt_key = "nico-nico-nii"
|
||||||
authd_registrations = false
|
authd_registrations = false
|
||||||
authd_extra_storage = "storage"
|
|
||||||
|
|
||||||
OptionParser.parse do |parser|
|
OptionParser.parse do |parser|
|
||||||
parser.on "-u file", "--passwd-file file", "passwd file." do |name|
|
parser.on "-s directory", "--storage directory", "Directory in which to store users." do |directory|
|
||||||
authd_passwd_file = name
|
authd_storage = directory
|
||||||
end
|
|
||||||
|
|
||||||
parser.on "-g file", "--group-file file", "group file." do |name|
|
|
||||||
authd_group_file = name
|
|
||||||
end
|
end
|
||||||
|
|
||||||
parser.on "-K file", "--key-file file", "JWT key file" do |file_name|
|
parser.on "-K file", "--key-file file", "JWT key file" do |file_name|
|
||||||
authd_jwt_key = File.read(file_name).chomp
|
authd_jwt_key = File.read(file_name).chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
parser.on "-S dir", "--extra-storage dir", "Storage for extra user-data." do |directory|
|
|
||||||
authd_extra_storage = directory
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on "-R", "--allow-registrations" do
|
parser.on "-R", "--allow-registrations" do
|
||||||
authd_registrations = true
|
authd_registrations = true
|
||||||
end
|
end
|
||||||
|
@ -196,9 +291,7 @@ OptionParser.parse do |parser|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
passwd = Passwd.new authd_passwd_file, authd_group_file
|
AuthD::Service.new(authd_storage, authd_jwt_key).tap do |authd|
|
||||||
|
|
||||||
AuthD::Service.new(passwd, authd_jwt_key, authd_extra_storage).tap do |authd|
|
|
||||||
authd.registrations_allowed = authd_registrations
|
authd.registrations_allowed = authd_registrations
|
||||||
end.run
|
end.run
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
require "json"
|
||||||
|
|
||||||
|
class AuthD::User
|
||||||
|
include JSON::Serializable
|
||||||
|
|
||||||
|
property login : String
|
||||||
|
property password_hash : String?
|
||||||
|
property uid : Int32
|
||||||
|
|
||||||
|
property mail_address : String
|
||||||
|
|
||||||
|
# FIXME: How would this profile be extended, replaced, checked?
|
||||||
|
property profile : Profile
|
||||||
|
|
||||||
|
class Profile
|
||||||
|
include JSON::Serializable
|
||||||
|
|
||||||
|
property full_name : String?
|
||||||
|
property description : String?
|
||||||
|
property avatar : String?
|
||||||
|
property website : String?
|
||||||
|
end
|
||||||
|
|
||||||
|
property registration_date : Time
|
||||||
|
|
||||||
|
property groups : Array(String)
|
||||||
|
|
||||||
|
# application name => configuration object
|
||||||
|
property configuration : Hash(String, JSON::Any)
|
||||||
|
end
|
||||||
|
|
||||||
|
class AuthD::Group
|
||||||
|
include JSON::Serializable
|
||||||
|
|
||||||
|
property name : String
|
||||||
|
property gid : Int32
|
||||||
|
property members : Array(String)
|
||||||
|
end
|
||||||
|
|
||||||
|
class AuthD::Storage
|
||||||
|
# FIXME: Create new groups and users, generate their ids.
|
||||||
|
def initialize(@storage_root)
|
||||||
|
@users = DODB::Hash(Int32, User).new "#{@storage_root}/users"
|
||||||
|
@users_by_login = @users.new_index "login", &.login
|
||||||
|
@users_by_group = @users.new_tags "groups", &.groups
|
||||||
|
|
||||||
|
@groups = DODB::Hash(Int32, Group).new "#{@storage_root}/groups"
|
||||||
|
@groups_by_name = new_index "name", &.name
|
||||||
|
@groups_by_member = new_tags "members", &.members
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
require "json"
|
||||||
|
|
||||||
|
class AuthD::Token
|
||||||
|
include JSON::Serializable
|
||||||
|
|
||||||
|
property login : String
|
||||||
|
property uid : Int32
|
||||||
|
|
||||||
|
def initialize(@login, @uid)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_h
|
||||||
|
{
|
||||||
|
:login => login,
|
||||||
|
:uid => uid
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(key)
|
||||||
|
JWT.encode to_h.to_json, key, JWT::Algorithm::HS256
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_s(key, str)
|
||||||
|
payload, meta = JWT.decode str, key, JWT::Algorithm::HS256
|
||||||
|
puts "PAYLOAD BELOW, BEWARE"
|
||||||
|
pp! payload
|
||||||
|
|
||||||
|
self.new payload["login"].as_s, payload["uid"].as_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
76
src/user.cr
76
src/user.cr
|
@ -1,41 +1,53 @@
|
||||||
require "json"
|
require "json"
|
||||||
|
|
||||||
require "passwd"
|
require "./token.cr"
|
||||||
|
|
||||||
class Passwd::User
|
class AuthD::User
|
||||||
JSON.mapping({
|
include JSON::Serializable
|
||||||
login: String,
|
|
||||||
password_hash: String,
|
|
||||||
uid: Int32,
|
|
||||||
gid: Int32,
|
|
||||||
home: String,
|
|
||||||
shell: String,
|
|
||||||
groups: Array(String),
|
|
||||||
full_name: String?,
|
|
||||||
office_phone_number: String?,
|
|
||||||
home_phone_number: String?,
|
|
||||||
other_contact: String?,
|
|
||||||
})
|
|
||||||
|
|
||||||
def sanitize!
|
enum PermissionLevel
|
||||||
@password_hash = "x"
|
None
|
||||||
self
|
Read
|
||||||
|
Edit
|
||||||
|
Admin
|
||||||
|
|
||||||
|
def to_json(o)
|
||||||
|
to_s.downcase.to_json o
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_h
|
# Public.
|
||||||
{
|
property login : String
|
||||||
:login => @login,
|
property uid : Int32
|
||||||
:password_hash => "x", # Not real hash in JWT.
|
property profile : JSON::Any?
|
||||||
:uid => @uid,
|
|
||||||
:gid => @gid,
|
# Private.
|
||||||
:home => @home,
|
property password_hash : String
|
||||||
:shell => @shell,
|
property permissions : Hash(String, Hash(String, PermissionLevel))
|
||||||
:groups => @groups,
|
property configuration : Hash(String, Hash(String, JSON::Any))
|
||||||
:full_name => @full_name,
|
|
||||||
:office_phone_number => @office_phone_number,
|
def to_token
|
||||||
:home_phone_number => @home_phone_number,
|
Token.new @login, @uid
|
||||||
:other_contact => @other_contact
|
end
|
||||||
}
|
|
||||||
|
def initialize(@uid, @login, @password_hash)
|
||||||
|
@permissions = Hash(String, Hash(String, PermissionLevel)).new
|
||||||
|
@configuration = Hash(String, Hash(String, JSON::Any)).new
|
||||||
|
end
|
||||||
|
|
||||||
|
class Public
|
||||||
|
include JSON::Serializable
|
||||||
|
|
||||||
|
property login : String
|
||||||
|
property uid : Int32
|
||||||
|
property profile : JSON::Any?
|
||||||
|
|
||||||
|
def initialize(@uid, @login, @profile)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_public : Public
|
||||||
|
Public.new @uid, @login, @profile
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue