packages feed

juandelacosa (empty) → 0.0.1

raw patch · 8 files changed

+320/−0 lines, 8 filesdep +basedep +base64-bytestringdep +bytestringsetup-changed

Dependencies added: base, base64-bytestring, bytestring, case-insensitive, docopt, entropy, http-types, mysql, mysql-simple, network, raw-strings-qq, resource-pool, unix, wai, warp

Files

+ ChangeLog.md view
@@ -0,0 +1,6 @@+0.0.1+=====++* Initial version+* Only reset user password on any request+
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2016, Zalora South East Asia Pte. Ltd++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,49 @@+Juan de la Cosa+===============++HTTP server for managing [MariaDB](http://mariadb.org/) users.+Designed to work behind [Sproxy](https://github.com/zalora/sproxy)+and assuming users' logins are their email addresses+(MariaDB allows up to 80 characters).++Currently it only let users get new passwords.++Requirements+============+Juan de la Cosa is written in Haskell with [GHC](http://www.haskell.org/ghc/).+All required Haskell libraries are listed in [juandelacosa.cabal](juandelacosa.cabal).+Use [cabal-install](http://www.haskell.org/haskellwiki/Cabal-Install)+to fetch and build all pre-requisites automatically.++Installation+============+    $ git clone https://github.com/zalora/juandelacosa.git+    $ cd juandelacosa+    $ cabal install++Usage+=====+Type `juandelacosa --help` to see usage summary:++    Usage:+      juandelacosa [options]++    Options:+      -f, --file=MYCNF         Read this MySQL client config file+      -g, --group=GROUP        Read this options group in the above file [default: client]++      -s, --socket=SOCK        Listen on this UNIX-socket [default: /tmp/juandelacosa.sock]+      -p, --port=PORT          Instead of UNIX-socket, listen on this TCP port (localhost)++      -h, --help               Show this message++Example:++    $ juandelacosa -p 8080+    $ curl http://localhost:8080 -H 'From: jack.frost@example.com'+      Tiw7CdJOmYxJBZ7J++The above request will change the password for 'jack.frost@example.com'@'%'+and return the new password to user. Once it's behind Sproxy+any user can get a new password in a secure way.+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ juandelacosa.cabal view
@@ -0,0 +1,46 @@+name: juandelacosa+version: 0.0.1+synopsis: Manage users in MariaDB >= 10.1.1+description:+  HTTP server for managing MariaDB users.  Designed to work behind+  Sproxy and assuming users' logins are their email addresses+  (MariaDB allows up to 80 characters).+license: MIT+license-file: LICENSE+author: Igor Pashev <pashev.igor@gmail.com>+maintainer: Igor Pashev <pashev.igor@gmail.com>+copyright: 2016, Zalora South East Asia Pte. Ltd+category: Databases, Web+build-type: Simple+extra-source-files: README.md ChangeLog.md+cabal-version: >= 1.20++source-repository head+  type: git+  location: https://github.com/zalora/juandelacosa.git++executable juandelacosa+    default-language: Haskell2010+    ghc-options: -Wall -static+    hs-source-dirs: src+    main-is: Main.hs+    other-modules:+        Application+      , Server+    build-depends:+          base              >= 4.8 && < 5+        , base64-bytestring >= 1.0+        , bytestring        >= 0.10+        , case-insensitive  >= 1.2+        , docopt            >= 0.7+        , entropy           >= 0.3+        , http-types        >= 0.9+        , mysql             >= 0.1+        , mysql-simple      >= 0.2+        , network           >= 2.6+        , raw-strings-qq    >= 1.0+        , resource-pool     >= 0.2+        , unix              >= 2.7+        , wai               >= 3.2+        , warp              >= 3.2+
+ src/Application.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}++module Application+(+  app+) where++import Data.ByteString.Base64 (encode)+import Data.Pool (Pool, withResource)+import Database.MySQL.Simple (Connection, execute)+import Network.HTTP.Types (status200, badRequest400, Header)+import Network.HTTP.Types.Header (hContentType)+import Network.Wai (Application, requestHeaders, responseLBS, Response)+import System.Entropy (getEntropy)+import System.IO (stderr)+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as LBS+import qualified Data.CaseInsensitive as CI+++app :: Pool Connection -> Application+app p request respond = do+  let+    headers = requestHeaders request+    from = readHeader "From" headers+  case from of+    Just login -> apiResetPassword p login >>= respond+    Nothing -> respond $ responseLBS+              badRequest400+              [(hContentType, "text/plain")]+              "Missing the From header"+++apiResetPassword :: Pool Connection -> BS.ByteString -> IO Response+apiResetPassword p login = withResource p $+  \c -> do+     blab ["SET PASSWORD FOR '", login, "'@'%'"]+     password <- BS.takeWhile (/= '=') . encode <$> getEntropy 12+     _ <- execute c "SET PASSWORD FOR ?@'%' = PASSWORD(?)"  [ login, password ]+     return $ responseLBS+              status200+              [(hContentType, "text/plain")]+              (LBS.fromStrict password)+++readHeader :: BS.ByteString -> [Header] -> Maybe BS.ByteString+readHeader h = lookup (CI.mk h)+++blab :: [BS.ByteString] -> IO ()+blab = BS.hPutStrLn stderr . BS.concat+
+ src/Main.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE QuasiQuotes #-}++module Main (+  main+) where++import Data.ByteString.Char8 (pack)+import Data.Maybe (fromJust)+import Data.Version (showVersion)+import Database.MySQL.Base (ConnectInfo(..))+import Database.MySQL.Base.Types (Option(ReadDefaultFile, ReadDefaultGroup))+import Paths_juandelacosa (version) -- from cabal+import System.Environment (getArgs)+import Text.RawString.QQ (r)+import qualified System.Console.Docopt.NoTH as O++import Server (server)++usage :: String+usage =  "juandelacosa " ++ showVersion version+  ++ " manage MariaDB user and roles" ++ [r|++Usage:+  juandelacosa [options]++Options:+  -f, --file=MYCNF         Read this MySQL client config file+  -g, --group=GROUP        Read this options group in the above file [default: client]++  -s, --socket=SOCK        Listen on this UNIX-socket [default: /tmp/juandelacosa.sock]+  -p, --port=PORT          Instead of UNIX-socket, listen on this TCP port (localhost)++  -h, --help               Show this message++|]++main :: IO()+main = do+  doco <- O.parseUsageOrExit usage+  args <- O.parseArgsOrExit doco =<< getArgs+  if args `O.isPresent` O.longOption "help"+  then putStrLn $ O.usage doco+  else do+    let+      file = O.getArg args $ O.longOption "file"+      group = fromJust $ O.getArg args $ O.longOption "group"+      port = O.getArg args $ O.longOption "port"+      socket = fromJust $ O.getArg args $ O.longOption "socket"+    -- XXX: mysql package maps empty strings to NULL+    -- which is what we need, see documentation for mysql_real_connect()+    let myInfo = ConnectInfo {+        connectDatabase = "",+        connectHost     = "",+        connectOptions  = case file of+                          Nothing -> []+                          Just f -> [ ReadDefaultFile f, ReadDefaultGroup (pack group) ],+        connectPassword = "",+        connectPath     = "",+        connectPort     = 0,+        connectSSL      = Nothing,+        connectUser     = ""+      }+    let listen = case port of+          Nothing -> Right socket+          Just p -> Left $ read p+    server listen myInfo+
+ src/Server.hs view
@@ -0,0 +1,78 @@+module Server+(+  server+) where++import Control.Exception.Base (throwIO, catch, bracket)+import Data.Bits ((.|.))+import Data.Pool (createPool, destroyAllResources)+import Database.MySQL.Base (ConnectInfo)+import Network.Socket (socket, bind, listen, close, maxListenQueue,+                       getSocketName, inet_addr,+                       Family(AF_UNIX, AF_INET), SocketType(Stream),+                       Socket, SockAddr(SockAddrUnix, SockAddrInet))+import Network.Wai.Handler.Warp (Port, defaultSettings, runSettingsSocket)+import System.IO (hPutStrLn, stderr)+import System.IO.Error (isDoesNotExistError)+import System.Posix.Files (removeLink, setFileMode, socketMode,+                           ownerReadMode, ownerWriteMode, groupReadMode, groupWriteMode)+import qualified Database.MySQL.Simple as MySQL++import Application (app)++type Listen = Either Port FilePath+++server :: Listen -> ConnectInfo -> IO ()+server socketSpec mysqlConnInfo =+  bracket+    ( do+      sock <- createSocket socketSpec+      mysql <- createPool+                (MySQL.connect mysqlConnInfo)+                MySQL.close+                1 -- stripes+                60 -- keep alive (seconds)+                10 -- max connections+      return (sock, mysql) )+    ( \(sock, mysql) -> do+      closeSocket sock+      destroyAllResources mysql )+    ( \(sock, mysql) -> do+      listen sock maxListenQueue+      runSettingsSocket defaultSettings sock (app mysql) )+++createSocket :: Listen -> IO Socket+createSocket (Right path) = do+  removeIfExists path+  sock <- socket AF_UNIX Stream 0+  bind sock $ SockAddrUnix path+  setFileMode path $ socketMode+                  .|. ownerWriteMode .|. ownerReadMode+                  .|. groupWriteMode .|. groupReadMode+  hPutStrLn stderr $ "Listening on UNIX socket `" ++ path ++ "'"+  return sock+createSocket (Left port) = do+  sock <- socket AF_INET Stream 0+  addr <- inet_addr "127.0.0.1"+  bind sock $ SockAddrInet (fromIntegral port) addr+  hPutStrLn stderr $ "Listening on localhost:" ++ show port+  return sock+++closeSocket :: Socket -> IO ()+closeSocket sock = do+  name <- getSocketName sock+  close sock+  case name of+    SockAddrUnix path -> removeIfExists path+    _ -> return ()+++removeIfExists :: FilePath -> IO ()+removeIfExists fileName = removeLink fileName `catch` handleExists+  where handleExists e+          | isDoesNotExistError e = return ()+          | otherwise = throwIO e+