Vapor 4 / Swift: разработка промежуточного программного обеспечения - PullRequest
4 голосов
/ 29 мая 2020

Я совершенно новичок в Swift и Vapor;

Я пытаюсь разработать промежуточное ПО для авторизации, которое вызывается из моей UserModel (объект Fluent Model) и имеет требуемый уровень доступа (int) и Путь (строка) и

  1. Гарантирует, что пользователь аутентифицирован,
  2. Проверяет, соответствует ли экземпляр UserModel.Accesslevel => Пропущенному уровню необходимого доступа и, если нет, перенаправляет пользователь к «Пути».

Я пытался пару дней и всегда приближался, но так и не понял, что мне нужно.

У меня есть следующее (пожалуйста извините за плохо названные объекты):

import Vapor

public protocol Authorizable: Authenticatable {
    associatedtype ProfileAccess: UserAuthorizable
}

/// status cached using  `UserAuthorizable'
public protocol UserAuthorizable: Authorizable {
    /// Session identifier type.
    associatedtype AccessLevel: LosslessStringConvertible

    /// Identifier identifier.
    var accessLevel: AccessLevel { get }
}

extension Authorizable {
    /// Basic middleware to redirect unauthenticated requests to the supplied path
    ///
    /// - parameters:
    ///    - path: The path to redirect to if the request is not authenticated
    public static func authorizeMiddleware(levelrequired: Int, path: String) -> Middleware {
            return AuthorizeUserMiddleware<Self>(Self.self, levelrequired: levelrequired, path: path)
    }
}

// Helper for creating authorization middleware.
///
//private final class AuthRedirectMiddleware<A>: Middleware
//where A: Authenticatable
final class AuthorizeUserMiddleware<A>: Middleware where A: Authorizable {
    let levelrequired: Int
    let path: String
    let authLevel : Int

init(_ authorizableType: A.Type = A.self, levelrequired: Int, path: String) {
    self.levelrequired = levelrequired
    self.path = path
}

/// See Middleware.respond
   public func respond(to req: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
        if req.auth.has(A.self) {
            print("--------")
            print(path)
            print(levelrequired)
**//            print(A.ProfileAccess.AccessLevel) <- list line fails because the A.ProfileAccess.AccessLevel is a type not value**
            print("--------")
        }
        return next.respond(to: req)
    }
}

Я добавил следующее в свою пользовательскую модель

extension UserModel: Authorizable
{
    typealias ProfileAccess = UserModel
}

extension UserModel: UserAuthorizable {
    typealias AccessLevel = Int
    var accessLevel: AccessLevel { self.userprofile! }
}

И Route как таковой

    // setup the authentication process
    let session = app.routes.grouped([
      UserModelSessionAuthenticator(),
      UserModelCredentialsAuthenticator(),
      UserModel.authorizeMiddleware(levelrequired: 255, path: "/login"), // this will redirect the user when unauthenticted
      UserModel.authRedirectMiddleware(path: "/login"), // this will redirect the user when unauthenticted
    ])

Путь и требуемый уровень правильно переданы, но я не могу получить экземпляр AccessLevel от текущего пользователя. (Я уверен, что у меня есть шляпа C ++, и из того, что я могу «догадаться», UserModel на самом деле не является заполненным экземпляром пользователя, хотя аутентификация уже завершена)

Я попытался слиться процесс 'SessionAuthenticator', использующий связанный тип для передачи информации об учетной записи.

Другой моей мыслью было проверить, аутентифицирован ли пользователь, и если да, то я могу с уверенностью предположить, что Session Cook ie содержит мой идентификатор пользователя и поэтому я мог бы вытащить пользователя из БД (снова) и проверить уровень доступа пользователей оттуда.

Я мог бы уйти отсюда, через пару дней я не знаю, какой способ лучше подход, любое руководство будет очень признательно.

Ура

1 Ответ

2 голосов
/ 29 мая 2020

Я использую подход Vapor 4 для согласования моей модели User с ModelAuthenticatable:

extension User:ModelAuthenticatable
{
    static let usernameKey = \User.$email
    static let passwordHashKey = \User.$password

    func verify(password: String) throws -> Bool
    {
        try Bcrypt.verify(password, created:self.password)
    }
}

В момент, когда я проверил, существует ли пользователь и пароль подтвержден, как указано выше, затем он регистрирует пользователя в системе:

request.auth.login(user)

После входа в систему я использую настраиваемый Middleware, который проверяет, вошел ли пользователь в систему, и «суперпользователь», если да, то ответ передается на следующий Middleware в цепочке, в противном случае он перенаправляет на домашнюю страницу / страницу входа, если нет.

struct SuperUserMiddleware:Middleware
{
    func respond(to request:Request, chainingTo next:Responder) -> EventLoopFuture<Response>
    {
        do
        {
            let user = try request.auth.require(User.self)
            if user.superUser { return next.respond(to:request) }
        }
        catch {}
        let redirect = request.redirect(to:"UNPRIVILEGED")
        return request.eventLoop.makeSucceededFuture(redirect)
    }
}

Я регистрирую SuperUserMiddleware и использую с некоторыми группами маршрутов в configure.swift как :

app.middleware.use(SessionsMiddleware(session:MemorySessions(storage:MemorySessions.Storage())))
app.middleware.use(User.sessionAuthenticator(.mysql))
let superUserMW = SuperUserMiddleware()
let userAuthSessionsMW = User.authenticator()
let events = app.grouped("/events").grouped(userAuthSessionsMW, superUserMW)
try EventRoutes(events)
...