Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patch Auth to support LDAP Authentication #77

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion snap.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ Library
unordered-containers >= 0.1.4 && < 0.3,
vector >= 0.7.1 && < 0.11,
vector-algorithms >= 0.4 && < 0.6,
xmlhtml >= 0.1 && < 0.3
xmlhtml >= 0.1 && < 0.3,
LDAP >= 0.6.8 && < 0.6.9
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, I'm not crazy about introducing LDAP as a dependency, especially since it binds to a C API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed -- is there some way we can make this a third-party downloadable package? The auth mechanism should be extensible, if it isn't we should put some work into that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IAuthBackend is not general enough to support remote authentication.

I'm ok if the patch only includes:

AuthManager.hs
Backends/JsonFile.hs
Handlers.hs

This patch will not introduce extra dependency.

Thank,
Ning

On 02/19/2013 01:24 AM, Gregory Collins wrote:

In snap.cabal:

@@ -168,7 +168,8 @@ Library
unordered-containers >= 0.1.4 && < 0.3,
vector >= 0.7.1 && < 0.11,
vector-algorithms >= 0.4 && < 0.6,

  • xmlhtml >= 0.1 && < 0.3
  • xmlhtml >= 0.1 && < 0.3,
  • LDAP >= 0.6.8 && < 0.6.9

Agreed -- is there some way we can make this a third-party
downloadable package? The auth mechanism should be extensible, if it
isn't we should put some work into that.


Reply to this email directly or view it on GitHub
https://github.com/snapframework/snap/pull/77/files#r3058889.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to making the necessary changes to snap to allow for a third party LDAP library.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope to get around to investigating this soon. Thank you for the patch.

On Friday, February 22, 2013 at 5:00 AM, Oliver Charles wrote:

In snap.cabal:

@@ -168,7 +168,8 @@ Library > unordered-containers >= 0.1.4 && < 0.3, > vector >= 0.7.1 && < 0.11, > vector-algorithms >= 0.4 && < 0.6, > - xmlhtml >= 0.1 && < 0.3 > + xmlhtml >= 0.1 && < 0.3, > + LDAP >= 0.6.8 && < 0.6.9
+1 to making the necessary changes to snap to allow for a third party LDAP library.


Reply to this email directly or view it on GitHub (https://github.com/snapframework/snap/pull/77/files#r3113746).


extensions:
BangPatterns,
Expand Down
2 changes: 2 additions & 0 deletions src/Snap/Snaplet/Auth/AuthManager.hs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class IAuthBackend r where
lookupByUserId :: r -> UserId -> IO (Maybe AuthUser)
lookupByLogin :: r -> Text -> IO (Maybe AuthUser)
lookupByRememberToken :: r -> Text -> IO (Maybe AuthUser)
authenticate :: r -> AuthUser -> Password -> IO (Maybe AuthFailure)
destroy :: r -> AuthUser -> IO ()


Expand Down Expand Up @@ -99,5 +100,6 @@ instance IAuthBackend (AuthManager b) where
lookupByUserId AuthManager{..} u = lookupByUserId backend u
lookupByLogin AuthManager{..} u = lookupByLogin backend u
lookupByRememberToken AuthManager{..} u = lookupByRememberToken backend u
authenticate AuthManager{..} u = authenticate backend u
destroy AuthManager{..} u = destroy backend u

7 changes: 7 additions & 0 deletions src/Snap/Snaplet/Auth/Backends/JsonFile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,13 @@ instance IAuthBackend JsonFileAuthManager where
f cache = getUid >>= getUser cache
where
getUid = HM.lookup token (tokenCache cache)

authenticate mgr usr pwd = case userPassword usr of
Just pwd' -> if checkPassword pwd pwd' then
return $ Nothing
else
return $ Just IncorrectPassword
Nothing -> return $ Just IncorrectPassword


------------------------------------------------------------------------------
Expand Down
116 changes: 116 additions & 0 deletions src/Snap/Snaplet/Auth/Backends/Ldap.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}


module Snap.Snaplet.Auth.Backends.Ldap
( initLdapAuthManager
, mkLdapAuthMgr
) where


import Control.Applicative
import Control.Monad.CatchIO (throw)
import Control.Monad.State
import Control.Concurrent.STM
import Data.Aeson
import qualified Data.Attoparsec as Atto
import qualified Data.ByteString.Lazy as LB
import qualified Data.ByteString as B
import qualified Data.Map as HM
import Data.Map (Map)
import Data.Maybe (fromJust, isJust)
import Data.Text (Text, unpack)
import qualified Data.Text as T
import Data.Time
import Web.ClientSession
import System.Directory

import Snap.Snaplet
import Snap.Snaplet.Auth.Types
import Snap.Snaplet.Auth.AuthManager
import Snap.Snaplet.Session
import qualified LDAP as L
import qualified Data.ByteString.UTF8 as Butf8

------------------------------------------------------------------------------
-- | Initialize a JSON file backed 'AuthManager'
initLdapAuthManager :: AuthSettings
-- ^ Authentication settings for your app
-> Text
-- ^ LDAP hostname
-> Integer
-- ^ LDAP port
-> Maybe Text
-- ^ username postfix
-> SnapletLens b SessionManager
-- ^ Lens into a 'SessionManager' auth snaplet will
-- use
-> SnapletInit b (AuthManager b)
initLdapAuthManager s hn prt postfix l = do
makeSnaplet
"LdapAuthManager"
"A snaplet providing user authentication using LDAP backend"
Nothing $ liftIO $ do
rng <- liftIO mkRNG
key <- getKey (asSiteKey s)
ldapMgr <- mkLdapAuthMgr hn prt postfix
return $! AuthManager {
backend = ldapMgr
, session = l
, activeUser = Nothing
, minPasswdLen = asMinPasswdLen s
, rememberCookieName = asRememberCookieName s
, rememberPeriod = asRememberPeriod s
, siteKey = key
, lockout = asLockout s
, randomNumberGenerator = rng
}


------------------------------------------------------------------------------
-- | Load/create a datafile into memory cache and return the manager.
--
-- This data type can be used by itself for batch/non-handler processing.
mkLdapAuthMgr :: Text -> Integer -> Maybe Text -> IO LdapAuthManager
mkLdapAuthMgr hn prt postfix = do
return $! LdapAuthManager {
hostname = hn
, port = prt
, queryUser = Nothing
, queryPwd = Nothing
, usernamePostfix = postfix
}


------------------------------------------------------------------------------
data LdapAuthManager = LdapAuthManager {
hostname :: Text
, port :: Integer
, queryUser :: Maybe Text
, queryPwd :: Maybe Text
, usernamePostfix :: Maybe Text
}

------------------------------------------------------------------------------
instance IAuthBackend LdapAuthManager where
save r = return . Right

destroy = error "LdapAuthManager: destroy is not yet implemented"

lookupByUserId mgr u@(UserId uid) = return $ Just (defAuthUser { userId = Just $ u
, userLogin = uid })
lookupByLogin mgr login = return $ Just (defAuthUser { userId = Just $ UserId login
, userLogin = login })

lookupByRememberToken mgr token = error "LdapAuthManager : lookupByRememberToken is not yet implemented"
authenticate mgr usr pwd = case pwd of
ClearText pwd' -> do
ld <- L.ldapInit (unpack $ hostname mgr) (fromInteger $ port mgr)
x' <- L.handleLDAP (return . Just . AuthError . show)
(L.ldapSimpleBind ld getDn (Butf8.toString pwd') >> return Nothing)
return x'
Encrypted _ -> return $ Just $ AuthError "cannot do LDAP authentication with encrypted password"
where getDn = let usrStr = T.unpack $ userLogin usr
in maybe usrStr (\x -> usrStr ++ T.unpack x) (usernamePostfix mgr)
15 changes: 5 additions & 10 deletions src/Snap/Snaplet/Auth/Handlers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,9 @@ checkPasswordAndLogin u pw =

where
auth :: AuthUser -> Handler b (AuthManager b) (Either AuthFailure AuthUser)
auth user =
case authenticatePassword user pw of
auth user = do
x <- authenticatePassword user pw
case x of
Just e -> do
markAuthFail user
return $ Left e
Expand Down Expand Up @@ -358,14 +359,8 @@ getSessionUserId = do
--
authenticatePassword :: AuthUser -- ^ Looked up from the back-end
-> Password -- ^ Check against this password
-> Maybe AuthFailure
authenticatePassword u pw = auth
where
auth = case userPassword u of
Nothing -> Just PasswordMissing
Just upw -> check $ checkPassword pw upw

check b = if b then Nothing else Just IncorrectPassword
-> Handler b (AuthManager b) (Maybe AuthFailure)
authenticatePassword u pw = withBackend (\r -> liftIO $ authenticate r u pw)


------------------------------------------------------------------------------
Expand Down