Skip to content

Commit 2ef0860

Browse files
authored
Merge pull request #62 from aviaviavi/license
Adding toodles pro tier, with license validation.
2 parents 4accf06 + 28313a6 commit 2ef0860

17 files changed

+303
-79
lines changed

.toodles.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ ignore:
88
- stack-work
99
- node_modules
1010
- Spec.hs
11+
- venv
12+
- vue.js
13+
- min.js
1114
# `flags` specify other keywords you might want to scan other than TODO
1215
# Hardcoded ones include TODO, FIXME, and XXX
1316
flags:

Dockerfile

+2
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ RUN apt-get update
3333
RUN apt-get install -y wget
3434
RUN wget -qO- https://get.haskellstack.org/ | sh
3535

36+
RUN pip install pycrypto
37+
3638
CMD ["toodles","-d","/repo/"]

README.md

+32-37
Original file line numberDiff line numberDiff line change
@@ -96,29 +96,7 @@ These languages will be scanned for any TODO's:
9696
Submit a PR if you'd like a language to be added. There will eventually be
9797
support for this to be user configurable
9898

99-
### Installing
100-
101-
The easiest way to get toodles is via [stack](https://docs.haskellstack.org).
102-
Just a `stack install --resolver=lts-12.14 toodles` and you're done! Alternatively, with GHC 8.4.3
103-
you can use [cabal](https://www.haskell.org/cabal/download.html). If there is
104-
desire for it I can look into precompiled distribution.
105-
106-
### Running
107-
108-
Invoking `toodles` with no arguments will treat the current directory as the
109-
project root and will start a server on port 9001. You can set these with the
110-
`-d` and `-p` flags, respectively.
111-
112-
113-
```bash
114-
# $ toodles -d <root directory of your project> -p <port to run server>
115-
# for more info run:
116-
# $ toodles --help
117-
$ toodles -d /path/to/your/project -p 9001
118-
# or simply
119-
$ toodles
120-
```
121-
#### Running with Docker
99+
### Running with Docker
122100

123101
You can run a pre-built toodles for your current directory via docker:
124102

@@ -142,23 +120,40 @@ $ docker run -it -v $(pwd):/repo -p 9001:9001 toodles
142120

143121
```
144122

145-
### Background
123+
### Installing manually
146124

147-
I work at a small startup called DotDashPay and over time the TODOs in our code
148-
base continued building up to the point where it was difficult to use them
149-
holistically. While the information in the TODOs was actually very useful and
150-
methodically written, the fact that were couldn't easily organize them started
151-
to weigh on us as mounting tech debt.
125+
The best way to install manually is with [stack](https://docs.haskellstack.org).
126+
Just a `stack install --resolver=lts-12.14 toodles` and you're done! Alternatively, with GHC 8.4.3
127+
you can use [cabal](https://www.haskell.org/cabal/download.html).
152128

153-
While not our main product focus, we try hard to find opportunities to build
154-
tools that make use of the organization schemes we already have in place, since
155-
doing so is a big win for us. Toodles became a nights and weekends side
156-
project to use the pre-existing TODO scheme we had spent years using, but had
157-
never effectively capitalized on.
129+
You'll also need to PyCrypto. If you have pip, you can run:
158130

159-
A quick plug if you also like building great tools, like working in a fast paced
160-
startup environment, and are located in the SF Bay Area: Reach out at
161-
[email protected] and come work with us!
131+
```bash
132+
$ pip install pycrypto
133+
```
134+
135+
If you've closed the toodles repo directly, you can also run
136+
137+
```bash
138+
$ cd path/to/toodles
139+
$ pip install -r requirements.txt
140+
```
141+
142+
### Running
143+
144+
Invoking `toodles` with no arguments will treat the current directory as the
145+
project root and will start a server on port 9001. You can set these with the
146+
`-d` and `-p` flags, respectively.
147+
148+
149+
```bash
150+
# $ toodles -d <root directory of your project> -p <port to run server>
151+
# for more info run:
152+
# $ toodles --help
153+
$ toodles -d /path/to/your/project -p 9001
154+
# or simply
155+
$ toodles
156+
```
162157

163158
### Contributing
164159

app/Main.hs

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module Main where
44

55
import Config
6+
import License
67
import Paths_toodles
78
import Server
89
import Types
@@ -15,6 +16,9 @@ import Text.Printf (printf)
1516

1617
main :: IO ()
1718
main = do
19+
dataDir <- getDataDir
20+
licenseRead <- readLicense (dataDir ++ "/toodles-license-public-key.pem") "/etc/toodles/license.json"
21+
let license = (either (BadLicense) (id) licenseRead)
1822
userArgs <- toodlesArgs >>= setAbsolutePath
1923
case userArgs of
2024
(ToodlesArgs _ _ _ _ True _) -> do
@@ -23,9 +27,9 @@ main = do
2327
_ -> do
2428
let webPort = fromMaybe 9001 $ port userArgs
2529
ref <- newIORef Nothing
26-
dataDir <- (++ "/web") <$> getDataDir
2730
putStrLn $ "serving on " ++ show webPort
28-
run webPort $ app $ ToodlesState ref dataDir
31+
tierRef <- newIORef license
32+
run webPort $ app $ ToodlesState ref (dataDir ++ "/web") tierRef
2933

3034
prettyFormat :: TodoEntry -> String
3135
prettyFormat (TodoEntryHead _ l a p n entryPriority f _ _ _ _ _ _) =

package.yaml

+21-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: toodles
2-
version: 1.0.3
2+
version: 1.1.0
33
github: "aviaviavi/toodles"
44
license: MIT
55
author: "Avi Press"
@@ -17,6 +17,8 @@ data-files:
1717
- web/css/*
1818
- web/fonts/*
1919
- web/img/*
20+
- verify.py
21+
- toodles-license-public-key.pem
2022

2123
synopsis: Manage the TODO entries in your code
2224
description:
@@ -32,7 +34,7 @@ ghc-options:
3234
- -Wcompat
3335

3436
dependencies:
35-
- base >= 4.0 && < 5
37+
- base >= 4.4.0 && < 5
3638

3739
# TODO (avi|p=3|#dependencies) - dependencies need to be relaxed and
3840
# fixed to include other ghc versions
@@ -44,22 +46,26 @@ library:
4446
- Config
4547
- ToodlesApi
4648
- Server
49+
- License
4750
dependencies:
48-
- hspec >= 2.4.4
49-
- hspec-expectations >=0.8.2
5051
- MissingH >=1.4.0.1
52+
- RSA >=2.3.0
5153
- aeson ==1.3.1.1
54+
- base64-bytestring ==1.0.0.1
5255
- blaze-html ==0.9.1.1
56+
- bytestring >=0.10.8.2
5357
- cmdargs ==0.10.20
5458
- directory ==1.3.1.5
5559
- extra ==1.6.13
5660
- megaparsec ==6.5.0
61+
- process >=1.6.3.0
5762
- regex-posix ==0.95.2
5863
- servant ==0.14.1
5964
- servant-blaze ==0.8
6065
- servant-server ==0.14.1
6166
- strict ==0.3.2
6267
- text ==1.2.3.1
68+
- time >=1.8.0.2
6369
- wai ==3.2.1.2
6470
- warp ==3.2.25
6571
- yaml ==0.8.32
@@ -77,21 +83,24 @@ executables:
7783
- -Wall
7884
- -with-rtsopts=-N
7985
dependencies:
80-
- hspec >= 2.4.4
81-
- hspec-expectations >=0.8.2
8286
- MissingH >=1.4.0.1
87+
- RSA >=2.3.0
8388
- aeson ==1.3.1.1
89+
- base64-bytestring ==1.0.0.1
8490
- blaze-html ==0.9.1.1
91+
- bytestring >=0.10.8.2
8592
- cmdargs ==0.10.20
8693
- directory ==1.3.1.5
8794
- extra ==1.6.13
8895
- megaparsec ==6.5.0
96+
- process >=1.6.3.0
8997
- regex-posix ==0.95.2
9098
- servant ==0.14.1
9199
- servant-blaze ==0.8
92100
- servant-server ==0.14.1
93101
- strict ==0.3.2
94102
- text ==1.2.3.1
103+
- time >=1.8.0.2
95104
- wai ==3.2.1.2
96105
- warp ==3.2.25
97106
- yaml ==0.8.32
@@ -109,21 +118,25 @@ tests:
109118
- -w
110119
dependencies:
111120
- toodles
112-
- hspec >= 2.4.4
113-
- hspec-expectations >=0.8.2
114121
- MissingH >=1.4.0.1
115122
- aeson ==1.3.1.1
123+
- base64-bytestring ==1.0.0.1
116124
- blaze-html ==0.9.1.1
125+
- bytestring >=0.10.8.2
117126
- cmdargs ==0.10.20
118127
- directory ==1.3.1.5
119128
- extra ==1.6.13
129+
- hspec >= 2.4.4
130+
- hspec-expectations >=0.8.2
120131
- megaparsec ==6.5.0
132+
- process >=1.6.3.0
121133
- regex-posix ==0.95.2
122134
- servant ==0.14.1
123135
- servant-blaze ==0.8
124136
- servant-server ==0.14.1
125137
- strict ==0.3.2
126138
- text ==1.2.3.1
139+
- time >=1.8.0.2
127140
- wai ==3.2.1.2
128141
- warp ==3.2.25
129142
- yaml ==0.8.32

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pycrypto

src/License.hs

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{-# LANGUAGE DeriveAnyClass #-}
2+
{-# LANGUAGE DeriveGeneric #-}
3+
{-# LANGUAGE GADTs #-}
4+
{-# LANGUAGE OverloadedStrings #-}
5+
{-# LANGUAGE ScopedTypeVariables #-}
6+
7+
8+
module License
9+
(
10+
UserTier(..),
11+
ToodlesLicense(..),
12+
readLicense
13+
) where
14+
15+
import Paths_toodles
16+
17+
import Data.Aeson
18+
import qualified Data.ByteString.Base64.Lazy as B64
19+
import qualified Data.ByteString.Lazy.Char8 as LB
20+
import Data.Text (Text)
21+
import qualified Data.Text as T
22+
import Data.Time.Clock.POSIX
23+
import GHC.Generics
24+
import System.Directory
25+
import System.Exit
26+
import System.Process
27+
28+
data UserTier
29+
= BadLicense String
30+
| NoLiscense
31+
| Individual
32+
| Commercial
33+
deriving (Show, Eq, Ord, Generic, ToJSON, FromJSON)
34+
35+
data License = License {
36+
payload :: ToodlesLicense,
37+
encoded :: Text,
38+
payloadSignature :: Text
39+
} deriving (Generic, FromJSON, ToJSON, Show)
40+
41+
data ToodlesLicense = ToodlesLicense
42+
{ validStart :: Integer
43+
, validEnd :: Integer
44+
, email :: Text
45+
, reference :: Text
46+
, product :: Text
47+
} deriving (Generic, FromJSON, ToJSON, Show)
48+
49+
readLicense :: FilePath -> FilePath -> IO (Either String UserTier)
50+
readLicense publicKeyPath licensePath = do
51+
licenseExists <- doesFileExist licensePath
52+
if not licenseExists then
53+
return $ Right NoLiscense
54+
else do
55+
parsedContents <- eitherDecodeFileStrict licensePath
56+
either (return . Left) (isLicenseValid publicKeyPath) parsedContents
57+
58+
isLicenseValid :: FilePath -> License -> IO (Either String UserTier)
59+
isLicenseValid publicKeyPath (License _ encodedPayload sig) = do
60+
dataDir <- getDataDir
61+
now <- ((* 1000) . round) `fmap` getPOSIXTime
62+
-- dependencies for license verification. for now, python
63+
-- TODO(#techdebt) - license verification should be done in haskell, this removed
64+
let args =
65+
[ dataDir ++ "/verify.py"
66+
, publicKeyPath
67+
, T.unpack sig
68+
, T.unpack encodedPayload
69+
]
70+
decodedPayload =
71+
decode (B64.decodeLenient . LB.pack $ T.unpack encodedPayload)
72+
(exitcode, stdout, stderr) <- readProcessWithExitCode "python" args ""
73+
putStrLn stderr
74+
return $
75+
let validated = ("True" == T.strip (T.pack stdout))
76+
in if (exitcode == ExitSuccess) &&
77+
validated && (maybe 0 validEnd decodedPayload >= now)
78+
then Right Commercial
79+
else Left "Invalid license file"

0 commit comments

Comments
 (0)