From 921e1faca15d85a06ca20d209c5606d6b0d3b64d Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 22 Nov 2017 13:15:45 +0100 Subject: [PATCH 1/4] Enable keter deployment --- config/keter.yml | 53 +++++++-------------------------------------- config/settings.yml | 2 +- stack.yaml | 3 +++ 3 files changed, 12 insertions(+), 46 deletions(-) diff --git a/config/keter.yml b/config/keter.yml index 7fc9ce4cb..1f4ed4f88 100644 --- a/config/keter.yml +++ b/config/keter.yml @@ -1,13 +1,6 @@ -# After you've edited this file, remove the following line to allow -# `yesod keter` to build your bundle. -user-edited: false +root: .. -# A Keter app is composed of 1 or more stanzas. The main stanza will define our -# web application. See the Keter documentation for more information on -# available stanzas. stanzas: - - # Your Yesod application. - type: webapp # Name of your executable. You are unlikely to need to change this. @@ -22,49 +15,19 @@ stanzas: args: [] hosts: - # You can specify one or more hostnames for your application to respond - # to. The primary hostname will be used for generating your application - # root. - - www.uniworx.com + - testworx.tcs.ifi.lmu.de - # Enable to force Keter to redirect to https - # Can be added to any stanza - requires-secure: false - - # Static files. - - type: static-files - hosts: - - static.uniworx.com - root: ../static - - # Uncomment to turn on directory listings. - # directory-listing: true - - # Redirect plain domain name to www. - - type: redirect - - hosts: - - uniworx.com - actions: - - host: www.uniworx.com - # secure: false - # port: 80 - - # Uncomment to switch to a non-permanent redirect. - # status: 303 + ssl: true # Use the following to automatically copy your bundle upon creation via `yesod # keter`. Uses `scp` internally, so you can set it to a remote destination # copy-to: user@host:/opt/keter/incoming/ - -# You can pass arguments to `scp` used above. This example limits bandwidth to -# 1024 Kbit/s and uses port 2222 instead of the default 22 -# copy-to-args: -# - "-l 1024" -# - "-P 2222" +copy-to: keter@testworx.tcs.ifi.lmu.de:/opt/keter/incoming/ +copy-to-args: + - "-P 30363" # If you would like to have Keter automatically create a PostgreSQL database # and set appropriate environment variables for it to be discovered, uncomment # the following line. -# plugins: -# postgres: true +plugins: + postgres: true diff --git a/config/settings.yml b/config/settings.yml index 4697c77da..2cee4c70c 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -8,7 +8,7 @@ ip-from-header: "_env:IP_FROM_HEADER:false" # Default behavior: determine the application root from the request headers. # Uncomment to set an explicit approot -#approot: "_env:APPROOT:http://localhost:3000" +approot: "_env:APPROOT:http://localhost:3000" # Optional values with the following production defaults. # In development, they default to the inverse. diff --git a/stack.yaml b/stack.yaml index c889ad49d..e3fef1fd3 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,5 +1,8 @@ flags: {} +docker: + enable: true nix: + enable: false packages: [] pure: false shell-file: ./stack.nix From 94f419394f8ca534678d64e29fa6e079f65362b8 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 22 Nov 2017 15:58:49 +0100 Subject: [PATCH 2/4] Nonfunctional LDAP auth --- config/keter.yml | 7 +++++++ config/settings.yml | 8 +++++++- docker/Dockerfile | 6 ++++++ package.yaml | 2 ++ src/Foundation.hs | 17 +++++++++++++++-- src/Settings.hs | 8 ++++++++ stack.yaml | 11 +++++++++++ 7 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 docker/Dockerfile diff --git a/config/keter.yml b/config/keter.yml index 1f4ed4f88..b6b7bd829 100644 --- a/config/keter.yml +++ b/config/keter.yml @@ -19,6 +19,13 @@ stanzas: ssl: true + forward-env: + - LDAPURI + - LDAPDN + - LDAPPW + - LDAPBN + - DUMMY_LOGIN + # Use the following to automatically copy your bundle upon creation via `yesod # keter`. Uses `scp` internally, so you can set it to a remote destination # copy-to: user@host:/opt/keter/incoming/ diff --git a/config/settings.yml b/config/settings.yml index 2cee4c70c..378f58f0f 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -18,7 +18,7 @@ approot: "_env:APPROOT:http://localhost:3000" # reload-templates: false # mutable-static: false # skip-combining: false -# auth-dummy-login : false +auth-dummy-login: "_env:DUMMY_LOGIN:false" # NB: If you need a numeric value (e.g. 123) to parse as a String, wrap it in single quotes (e.g. "_env:PGPASS:'123'") # See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings @@ -32,6 +32,12 @@ database: database: "_env:PGDATABASE:uniworx" poolsize: "_env:PGPOOLSIZE:10" +ldap: + uri: "_env:LDAPURI:ldap://localhost:389" + dn: "_env:LDAPDN:uniworx" + password: "_env:LDAPPW:" + basename: "_env:LDAPBN:" + cryptoid-keyfile: "_env:CRYPTOID_KEYFILE:cryptoid_key.bf" copyright: Insert copyright statement here diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..310b609cc --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,6 @@ +FROM fpco/stack-build:lts-9.3 + +ENV DEBIAN_FRONTEND noninteractive + +RUN apt-get update +RUN apt-get install libldap2-dev libsasl2-dev \ No newline at end of file diff --git a/package.yaml b/package.yaml index 2a11a9332..536c40907 100644 --- a/package.yaml +++ b/package.yaml @@ -72,6 +72,8 @@ dependencies: - generic-deriving - blaze-html - conduit-resumablesink >=0.2 +- yesod-auth-ldap +- LDAP # The library contains all of our application code. The executable # defined below is just a thin wrapper. diff --git a/src/Foundation.hs b/src/Foundation.hs index 67e9a8933..8093fb9a1 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -17,8 +17,10 @@ import Text.Jasmine (minifym) -- Used only when in "auth-dummy-login" setting is enabled. import Yesod.Auth.Dummy +import Yesod.Auth.LDAP + +import LDAP.Data (LDAPScope(..)) -import Yesod.Auth.OpenId (authOpenId, IdentifierType (Claimed)) import Yesod.Default.Util (addStaticContentExternal) import Yesod.Core.Types (Logger) import qualified Yesod.Core.Unsafe as Unsafe @@ -280,12 +282,23 @@ instance YesodAuth UniWorX where } -- You can add other plugins like Google Email, email or OAuth here - authPlugins app = [authOpenId Claimed []] ++ extraAuthPlugins + authPlugins app = [genericAuthLDAP $ ldapConfig app] ++ extraAuthPlugins -- Enable authDummy login if enabled. where extraAuthPlugins = [authDummy | appAuthDummyLogin $ appSettings app] authHttpManager = getHttpManager +ldapConfig :: UniWorX -> LDAPConfig +ldapConfig app@(appSettings -> settings) = LDAPConfig + { usernameFilter = ("userPrincipalName=" <>) + , identifierModifier = \n _ -> n + , ldapUri = appLDAPURI settings + , initDN = appLDAPDN settings + , initPass = appLDAPPw settings + , baseDN = appLDAPBaseName settings + , ldapScope = LdapScopeDefault + } + -- | Access function to determine if a user is logged in. isAuthenticated :: Handler AuthResult isAuthenticated = do diff --git a/src/Settings.hs b/src/Settings.hs index 437985178..e595cdc59 100644 --- a/src/Settings.hs +++ b/src/Settings.hs @@ -43,6 +43,11 @@ data AppSettings = AppSettings -- ^ Get the IP address from the header when logging. Useful when sitting -- behind a reverse proxy. + , appLDAPURI :: String + , appLDAPDN :: String + , appLDAPPw :: String + , appLDAPBaseName :: Maybe String + , appDetailedRequestLogging :: Bool -- ^ Use detailed request logging system , appShouldLogAll :: Bool @@ -80,6 +85,9 @@ instance FromJSON AppSettings where appPort <- o .: "port" appIpFromHeader <- o .: "ip-from-header" + ( appLDAPURI, appLDAPDN, appLDAPPw, appLDAPBaseName ) + <- (=<< o .: "ldap") . withObject "LDAP" $ \obj -> (,,,) <$> obj .: "uri" <*> obj .: "dn" <*> obj .: "password" <*> obj .:? "basename" + appDetailedRequestLogging <- o .:? "detailed-logging" .!= defaultDev appShouldLogAll <- o .:? "should-log-all" .!= defaultDev appReloadTemplates <- o .:? "reload-templates" .!= defaultDev diff --git a/stack.yaml b/stack.yaml index e3fef1fd3..761bb647d 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,6 +1,7 @@ flags: {} docker: enable: true + image: uniworx nix: enable: false packages: [] @@ -13,6 +14,14 @@ packages: git: https://github.com/pngwjpgh/zip-stream.git commit: 9272bbed000928d500febad1cdc98d1da29d399e extra-dep: true +- location: + git: https://github.com/mlitchard/yesod-auth-ldap.git + commit: 69e08ef687ab96df3352ff4267562135453c6f02 + extra-dep: true +- location: + git: https://github.com/mlitchard/authenticate-ldap.git + commit: cc2770024766a8fa29d3086688df60aaf65fb954 + extra-dep: true extra-deps: - colonnade-1.1.1 - yesod-colonnade-1.1.0 @@ -25,4 +34,6 @@ extra-deps: - encoding-0.8.2 - regex-compat-0.93.1 + +- LDAP-0.6.11 resolver: lts-9.3 From 2c188926a68639457e02ac40188043bb17e4b65d Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 22 Nov 2017 16:47:50 +0100 Subject: [PATCH 3/4] I haz a login \o/ --- src/Foundation.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Foundation.hs b/src/Foundation.hs index 8093fb9a1..76729e7b4 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -296,7 +296,7 @@ ldapConfig app@(appSettings -> settings) = LDAPConfig , initDN = appLDAPDN settings , initPass = appLDAPPw settings , baseDN = appLDAPBaseName settings - , ldapScope = LdapScopeDefault + , ldapScope = LdapScopeSubtree } -- | Access function to determine if a user is logged in. From 57cac79d693fb4a320f66c0da1b717ed7624368c Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 22 Nov 2017 18:33:24 +0100 Subject: [PATCH 4/4] Synchronise matrikelnummer from LDAP --- config/keter.yml | 2 ++ config/settings.yml | 4 ++-- models | 2 +- src/Foundation.hs | 51 ++++++++++++++++++++++++++++++--------------- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/config/keter.yml b/config/keter.yml index b6b7bd829..24177b0fc 100644 --- a/config/keter.yml +++ b/config/keter.yml @@ -25,6 +25,8 @@ stanzas: - LDAPPW - LDAPBN - DUMMY_LOGIN + - DETAILED_LOGGING + - LOG_ALL # Use the following to automatically copy your bundle upon creation via `yesod # keter`. Uses `scp` internally, so you can set it to a remote destination diff --git a/config/settings.yml b/config/settings.yml index 378f58f0f..4cf378423 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -13,8 +13,8 @@ approot: "_env:APPROOT:http://localhost:3000" # Optional values with the following production defaults. # In development, they default to the inverse. # -# detailed-logging: false -# should-log-all: false +detailed-logging: "_env:DETAILED_LOGGING:false" +should-log-all: "_env:LOG_ALL:false" # reload-templates: false # mutable-static: false # skip-combining: false diff --git a/models b/models index 7b7d0a666..4f016f828 100644 --- a/models +++ b/models @@ -1,7 +1,7 @@ User plugin Text ident Text - matrikelnummer Text + matrikelnummer Text Maybe UniqueAuthentication plugin ident Term json name TermIdentifier diff --git a/src/Foundation.hs b/src/Foundation.hs index 76729e7b4..9f853da68 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -6,6 +6,7 @@ {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE PatternGuards #-} module Foundation where @@ -16,10 +17,12 @@ import Text.Hamlet (hamletFile) import Text.Jasmine (minifym) -- Used only when in "auth-dummy-login" setting is enabled. +import Yesod.Auth.Message import Yesod.Auth.Dummy import Yesod.Auth.LDAP import LDAP.Data (LDAPScope(..)) +import LDAP.Search (LDAPEntry(..)) import Yesod.Default.Util (addStaticContentExternal) import Yesod.Core.Types (Logger) @@ -265,21 +268,29 @@ instance YesodAuth UniWorX where -- Override the above two destinations when a Referer: header is present redirectToReferer _ = True - authenticate Creds{..} = runDB $ do - let (plugin, ident) - | credsPlugin == "dummy" - , [dummyPlugin, dummyIdent] <- Text.splitOn ":" credsIdent - = (dummyPlugin, dummyIdent) - | otherwise - = (credsPlugin, credsIdent) - x <- getBy $ UniqueAuthentication plugin ident - case x of - Just (Entity uid _) -> return $ Authenticated uid - Nothing -> Authenticated <$> insert User - { userPlugin = plugin - , userIdent = ident - , userMatrikelnummer = "DummyMatrikel" - } + authenticate creds@(Creds{..}) = runDB $ do + let (userPlugin, userIdent) + | isDummy + , [dummyPlugin, dummyIdent] <- Text.splitOn ":" credsIdent + = (dummyPlugin, dummyIdent) + | otherwise + = (credsPlugin, credsIdent) + isDummy = credsPlugin == "dummy" + uAuth = UniqueAuthentication userPlugin userIdent + + $logDebugS "auth" $ tshow ((userPlugin, userIdent), creds) + + case isDummy of + True -> + maybe (UserError $ IdentifierNotFound credsIdent) (Authenticated . entityKey) <$> getBy uAuth + False -> do + let + userMatrikelnummer = lookup "LMU-Stud-Matrikelnummer" credsExtra + + newUser = User{..} + userUpdate = [ UserMatrikelnummer =. userMatrikelnummer + ] + Authenticated . entityKey <$> upsertBy uAuth newUser userUpdate -- You can add other plugins like Google Email, email or OAuth here authPlugins app = [genericAuthLDAP $ ldapConfig app] ++ extraAuthPlugins @@ -290,14 +301,20 @@ instance YesodAuth UniWorX where ldapConfig :: UniWorX -> LDAPConfig ldapConfig app@(appSettings -> settings) = LDAPConfig - { usernameFilter = ("userPrincipalName=" <>) - , identifierModifier = \n _ -> n + { usernameFilter = \u -> principalName <> "=" <> u + , identifierModifier , ldapUri = appLDAPURI settings , initDN = appLDAPDN settings , initPass = appLDAPPw settings , baseDN = appLDAPBaseName settings , ldapScope = LdapScopeSubtree } + where + principalName :: IsString a => a + principalName = "userPrincipalName" + identifierModifier _ entry = case lookup principalName $ leattrs entry of + Just [n] -> Text.pack n + _ -> error "Could not determine user principal name" -- | Access function to determine if a user is logged in. isAuthenticated :: Handler AuthResult