Skip to content

Commit 436ca47

Browse files
author
Charles-Antoine Mathieu
authored
Merge pull request src-d#214 from camathieu/1.2.1
1.2.1
2 parents 42ac058 + aa31608 commit 436ca47

File tree

10 files changed

+229
-95
lines changed

10 files changed

+229
-95
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,15 +173,17 @@ Suitable for distributed / High Availability deployment.
173173
### Authentication
174174

175175
Plik can authenticate users using Google and/or OVH API.
176-
Once authenticated the only call Plik will ever make to those API is get the user ID, name and email.
176+
Once authenticated the only call Plik will ever make to those API is to get the user ID, name and email.
177177
Plik will never forward any upload data or metadata to any third party.
178178
If source IP address restriction is enabled, user accounts can only be created from trusted IPs. But then
179-
authenticated users can upload files without source IP restriction.
179+
authenticated users can upload files without source IP restriction.
180+
It is also possible to deny unauthenticated uploads totally.
180181

181182
- **Google** :
182183
- You'll need to create a new application in the [Google Developper Console](https://console.developers.google.com)
183184
- You'll be handed a Google API ClientID and a Google API ClientSecret that you'll need to put in the plikd.cfg file.
184185
- Do not forget to whitelist valid origin and redirect url ( https://yourdomain/auth/google/callback ) for your domain.
186+
- It is possible to whitelist only one or more email domains.
185187

186188
- **OVH** :
187189
- You'll need to create a new application in the OVH API : https://eu.api.ovh.com/createApp/

server/common/config.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,14 @@ import (
4141

4242
// Configuration object
4343
type Configuration struct {
44-
LogLevel string `json:"-"`
45-
ListenAddress string `json:"-"`
46-
ListenPort int `json:"-"`
47-
MaxFileSize int64 `json:"maxFileSize"`
48-
MaxFilePerUpload int `json:"maxFilePerUpload"`
44+
LogLevel string `json:"-"`
45+
46+
ListenAddress string `json:"-"`
47+
ListenPort int `json:"-"`
48+
Path string `json:"-"`
49+
50+
MaxFileSize int64 `json:"maxFileSize"`
51+
MaxFilePerUpload int `json:"maxFilePerUpload"`
4952

5053
DefaultTTL int `json:"defaultTTL"`
5154
MaxTTL int `json:"maxTTL"`
@@ -65,13 +68,15 @@ type Configuration struct {
6568
SourceIPHeader string `json:"-"`
6669
UploadWhitelist []string `json:"-"`
6770

68-
Authentication bool `json:"authentication"`
69-
GoogleAuthentication bool `json:"googleAuthentication"`
70-
GoogleAPISecret string `json:"-"`
71-
GoogleAPIClientID string `json:"-"`
72-
OvhAuthentication bool `json:"ovhAuthentication"`
73-
OvhAPIKey string `json:"-"`
74-
OvhAPISecret string `json:"-"`
71+
Authentication bool `json:"authentication"`
72+
NoAnonymousUploads bool `json:"-"`
73+
GoogleAuthentication bool `json:"googleAuthentication"`
74+
GoogleAPISecret string `json:"-"`
75+
GoogleAPIClientID string `json:"-"`
76+
GoogleValidDomains []string `json:"-"`
77+
OvhAuthentication bool `json:"ovhAuthentication"`
78+
OvhAPIKey string `json:"-"`
79+
OvhAPISecret string `json:"-"`
7580

7681
MetadataBackend string `json:"-"`
7782
MetadataBackendConfig map[string]interface{} `json:"-"`
@@ -103,8 +108,6 @@ func NewConfiguration() (config *Configuration) {
103108
config.DefaultTTL = 2592000 // 30 days
104109
config.MaxTTL = 0
105110
config.SslEnabled = false
106-
config.SslCert = ""
107-
config.SslKey = ""
108111
config.StreamMode = true
109112
return
110113
}
@@ -114,6 +117,7 @@ func NewConfiguration() (config *Configuration) {
114117
// override default params
115118
func LoadConfiguration(file string) {
116119
Config = NewConfiguration()
120+
117121
if _, err := toml.DecodeFile(file, Config); err != nil {
118122
Logger().Fatalf("Unable to load config file %s : %s", file, err)
119123
}
@@ -125,6 +129,8 @@ func LoadConfiguration(file string) {
125129
Logger().SetFlags(logger.Fdate | logger.Flevel | logger.FfixedSizeLevel)
126130
}
127131

132+
Config.Path = strings.TrimSuffix(Config.Path, "/")
133+
128134
// Do user specified a ApiKey and ApiSecret for Yubikey
129135
if Config.YubikeyEnabled {
130136
yubiAuth, err := yubigo.NewYubiAuth(Config.YubikeyAPIKey, Config.YubikeyAPISecret)
@@ -165,10 +171,12 @@ func LoadConfiguration(file string) {
165171

166172
if !Config.GoogleAuthentication && !Config.OvhAuthentication {
167173
Config.Authentication = false
174+
Config.NoAnonymousUploads = false
168175
}
169176

170177
if Config.MetadataBackend == "file" {
171178
Config.Authentication = false
179+
Config.NoAnonymousUploads = false
172180
}
173181

174182
if Config.DownloadDomain != "" {

server/common/context.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,36 @@ func Fail(ctx *juliet.Context, req *http.Request, resp http.ResponseWriter, mess
145145
}
146146
}
147147
if redirect {
148-
http.Redirect(resp, req, fmt.Sprintf("/#!/?err=%s&errcode=%d&uri=%s", message, status, req.RequestURI), 301)
148+
http.Redirect(resp, req, fmt.Sprintf("%s/#!/?err=%s&errcode=%d&uri=%s", Config.Path, message, status, req.RequestURI), 301)
149149
return
150150
}
151151
}
152152

153153
http.Error(resp, NewResult(message, nil).ToJSONString(), status)
154154
}
155+
156+
// StripPrefix returns a handler that serves HTTP requests
157+
// removing the given prefix from the request URL's Path
158+
// It differs from http.StripPrefix by defaulting to "/" and not ""
159+
func StripPrefix(prefix string, handler http.Handler) http.Handler {
160+
if prefix == "" || prefix == "/" {
161+
return handler
162+
}
163+
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
164+
// Relative paths to javascript, css, ... imports won't work without a tailing slash
165+
if req.URL.Path == prefix {
166+
http.Redirect(resp, req, prefix+"/", 301)
167+
return
168+
}
169+
if p := strings.TrimPrefix(req.URL.Path, prefix); len(p) < len(req.URL.Path) {
170+
req.URL.Path = p
171+
} else {
172+
http.NotFound(resp, req)
173+
return
174+
}
175+
if !strings.HasPrefix(req.URL.Path, "/") {
176+
req.URL.Path = "/" + req.URL.Path
177+
}
178+
handler.ServeHTTP(resp, req)
179+
})
180+
}

server/handlers/addFile.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ func AddFile(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request) {
5757
return
5858
}
5959

60+
// Check anonymous user uploads
61+
if common.Config.NoAnonymousUploads {
62+
user := common.GetUser(ctx)
63+
if user == nil {
64+
log.Warning("Unable to add file from anonymous user")
65+
common.Fail(ctx, req, resp, "Unable to add file from anonymous user. Please login or use a cli token.", 403)
66+
return
67+
}
68+
}
69+
6070
// Check authorization
6171
if !upload.IsAdmin {
6272
log.Warningf("Unable to add file : unauthorized")

server/handlers/createUpload.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,16 @@ func CreateUpload(ctx *juliet.Context, resp http.ResponseWriter, req *http.Reque
4747
log := common.GetLogger(ctx)
4848

4949
user := common.GetUser(ctx)
50-
if user == nil && !common.IsWhitelisted(ctx) {
51-
log.Warning("Unable to create upload from untrusted source IP address")
52-
common.Fail(ctx, req, resp, "Unable to create upload from untrusted source IP address. Please login or use a cli token.", 403)
53-
return
50+
if user == nil {
51+
if common.Config.NoAnonymousUploads {
52+
log.Warning("Unable to create upload from anonymous user")
53+
common.Fail(ctx, req, resp, "Unable to create upload from anonymous user. Please login or use a cli token.", 403)
54+
return
55+
} else if !common.IsWhitelisted(ctx) {
56+
log.Warning("Unable to create upload from untrusted source IP address")
57+
common.Fail(ctx, req, resp, "Unable to create upload from untrusted source IP address. Please login or use a cli token.", 403)
58+
return
59+
}
5460
}
5561

5662
upload := common.NewUpload()

server/handlers/google.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ package handlers
3232
import (
3333
"fmt"
3434
"net/http"
35+
"strings"
3536
"time"
3637

3738
"github.com/dgrijalva/jwt-go"
@@ -63,7 +64,7 @@ func GoogleLogin(ctx *juliet.Context, resp http.ResponseWriter, req *http.Reques
6364
origin := req.Header.Get("referer")
6465
if origin == "" {
6566
log.Warning("Missing referer header")
66-
common.Fail(ctx, req, resp, "Missing referer herader", 400)
67+
common.Fail(ctx, req, resp, "Missing referer header", 400)
6768
return
6869
}
6970

@@ -207,6 +208,26 @@ func GoogleCallback(ctx *juliet.Context, resp http.ResponseWriter, req *http.Req
207208
user.Login = userInfo.Email
208209
user.Name = userInfo.Name
209210
user.Email = userInfo.Email
211+
components := strings.Split(user.Email, "@")
212+
213+
// Accepted user domain checking
214+
goodDomain := false
215+
if len(common.Config.GoogleValidDomains) > 0 {
216+
for _, validDomain := range common.Config.GoogleValidDomains {
217+
if strings.Compare(components[1], validDomain) == 0 {
218+
goodDomain = true
219+
}
220+
}
221+
} else {
222+
goodDomain = true
223+
}
224+
225+
if !goodDomain {
226+
// User not from accepted google domains list
227+
log.Warningf("Unacceptable user domain : %s", components[1])
228+
common.Fail(ctx, req, resp, fmt.Sprintf("Authentification error : Unauthorized dommain %s", components[1]), 403)
229+
return
230+
}
210231

211232
// Save user to metadata backend
212233
err = metadataBackend.GetMetaDataBackend().SaveUser(ctx, user)
@@ -263,5 +284,5 @@ func GoogleCallback(ctx *juliet.Context, resp http.ResponseWriter, req *http.Req
263284
xsrfCookie.Path = "/"
264285
http.SetCookie(resp, xsrfCookie)
265286

266-
http.Redirect(resp, req, "/#!/login", 301)
287+
http.Redirect(resp, req, common.Config.Path+"/#!/login", 301)
267288
}

server/handlers/ovh.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func OvhLogin(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request)
107107
origin := req.Header.Get("referer")
108108
if origin == "" {
109109
log.Warning("Missing referer header")
110-
common.Fail(ctx, req, resp, "Missing referer herader", 400)
110+
common.Fail(ctx, req, resp, "Missing referer header", 400)
111111
return
112112
}
113113

@@ -377,5 +377,5 @@ func OvhCallback(ctx *juliet.Context, resp http.ResponseWriter, req *http.Reques
377377
xsrfCookie.Path = "/"
378378
http.SetCookie(resp, xsrfCookie)
379379

380-
http.Redirect(resp, req, "/#!/login", 301)
380+
http.Redirect(resp, req, common.Config.Path+"/#!/login", 301)
381381
}

server/plik.go

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -97,39 +97,42 @@ func main() {
9797
getFileChain := juliet.NewChain(middleware.Upload, middleware.Yubikey, middleware.File)
9898

9999
// HTTP Api routes configuration
100-
r := mux.NewRouter()
101-
r.Handle("/config", stdChain.Then(handlers.GetConfiguration)).Methods("GET")
102-
r.Handle("/version", stdChain.Then(handlers.GetVersion)).Methods("GET")
103-
r.Handle("/upload", tokenChain.Then(handlers.CreateUpload)).Methods("POST")
104-
r.Handle("/upload/{uploadID}", authChain.Append(middleware.Upload).Then(handlers.GetUpload)).Methods("GET")
105-
r.Handle("/upload/{uploadID}", authChain.Append(middleware.Upload).Then(handlers.RemoveUpload)).Methods("DELETE")
106-
r.Handle("/file/{uploadID}", tokenChain.Append(middleware.Upload).Then(handlers.AddFile)).Methods("POST")
107-
r.Handle("/file/{uploadID}/{fileID}/{filename}", tokenChain.Append(middleware.Upload, middleware.File).Then(handlers.AddFile)).Methods("POST")
108-
r.Handle("/file/{uploadID}/{fileID}/{filename}", authChain.Append(middleware.Upload, middleware.File).Then(handlers.RemoveFile)).Methods("DELETE")
109-
r.Handle("/file/{uploadID}/{fileID}/{filename}", authChainWithRedirect.AppendChain(getFileChain).Then(handlers.GetFile)).Methods("HEAD", "GET")
110-
r.Handle("/file/{uploadID}/{fileID}/{filename}/yubikey/{yubikey}", authChainWithRedirect.AppendChain(getFileChain).Then(handlers.GetFile)).Methods("HEAD", "GET")
111-
r.Handle("/stream/{uploadID}/{fileID}/{filename}", tokenChain.Append(middleware.Upload, middleware.File).Then(handlers.AddFile)).Methods("POST")
112-
r.Handle("/stream/{uploadID}/{fileID}/{filename}", authChain.Append(middleware.Upload, middleware.File).Then(handlers.RemoveFile)).Methods("DELETE")
113-
r.Handle("/stream/{uploadID}/{fileID}/{filename}", authChainWithRedirect.AppendChain(getFileChain).Then(handlers.GetFile)).Methods("HEAD", "GET")
114-
r.Handle("/stream/{uploadID}/{fileID}/{filename}/yubikey/{yubikey}", authChainWithRedirect.AppendChain(getFileChain).Then(handlers.GetFile)).Methods("HEAD", "GET")
115-
r.Handle("/archive/{uploadID}/{filename}", authChainWithRedirect.Append(middleware.Upload, middleware.Yubikey).Then(handlers.GetArchive)).Methods("HEAD", "GET")
116-
r.Handle("/archive/{uploadID}/{filename}/yubikey/{yubikey}", authChainWithRedirect.Append(middleware.Upload, middleware.Yubikey).Then(handlers.GetArchive)).Methods("HEAD", "GET")
117-
r.Handle("/auth/google/login", authChain.Then(handlers.GoogleLogin)).Methods("GET")
118-
r.Handle("/auth/google/callback", stdChainWithRedirect.Then(handlers.GoogleCallback)).Methods("GET")
119-
r.Handle("/auth/ovh/login", authChain.Then(handlers.OvhLogin)).Methods("GET")
120-
r.Handle("/auth/ovh/callback", stdChainWithRedirect.Then(handlers.OvhCallback)).Methods("GET")
121-
r.Handle("/auth/logout", authChain.Then(handlers.Logout)).Methods("GET")
122-
r.Handle("/me", authChain.Then(handlers.UserInfo)).Methods("GET")
123-
r.Handle("/me", authChain.Then(handlers.DeleteAccount)).Methods("DELETE")
124-
r.Handle("/me/token", authChain.Then(handlers.CreateToken)).Methods("POST")
125-
r.Handle("/me/token/{token}", authChain.Then(handlers.RevokeToken)).Methods("DELETE")
126-
r.Handle("/me/uploads", authChain.Then(handlers.GetUserUploads)).Methods("GET")
127-
r.Handle("/me/uploads", authChain.Then(handlers.RemoveUserUploads)).Methods("DELETE")
128-
r.Handle("/qrcode", stdChain.Then(handlers.GetQrCode)).Methods("GET")
129-
r.PathPrefix("/clients/").Handler(http.StripPrefix("/clients/", http.FileServer(http.Dir("../clients"))))
130-
r.PathPrefix("/changelog/").Handler(http.StripPrefix("/changelog/", http.FileServer(http.Dir("../changelog"))))
131-
r.PathPrefix("/").Handler(http.FileServer(http.Dir("./public/")))
132-
http.Handle("/", r)
100+
router := mux.NewRouter()
101+
router.Handle("/config", stdChain.Then(handlers.GetConfiguration)).Methods("GET")
102+
router.Handle("/version", stdChain.Then(handlers.GetVersion)).Methods("GET")
103+
router.Handle("/upload", tokenChain.Then(handlers.CreateUpload)).Methods("POST")
104+
router.Handle("/upload/{uploadID}", authChain.Append(middleware.Upload).Then(handlers.GetUpload)).Methods("GET")
105+
router.Handle("/upload/{uploadID}", authChain.Append(middleware.Upload).Then(handlers.RemoveUpload)).Methods("DELETE")
106+
router.Handle("/file/{uploadID}", tokenChain.Append(middleware.Upload).Then(handlers.AddFile)).Methods("POST")
107+
router.Handle("/file/{uploadID}/{fileID}/{filename}", tokenChain.Append(middleware.Upload, middleware.File).Then(handlers.AddFile)).Methods("POST")
108+
router.Handle("/file/{uploadID}/{fileID}/{filename}", authChain.Append(middleware.Upload, middleware.File).Then(handlers.RemoveFile)).Methods("DELETE")
109+
router.Handle("/file/{uploadID}/{fileID}/{filename}", authChainWithRedirect.AppendChain(getFileChain).Then(handlers.GetFile)).Methods("HEAD", "GET")
110+
router.Handle("/file/{uploadID}/{fileID}/{filename}/yubikey/{yubikey}", authChainWithRedirect.AppendChain(getFileChain).Then(handlers.GetFile)).Methods("HEAD", "GET")
111+
router.Handle("/stream/{uploadID}/{fileID}/{filename}", tokenChain.Append(middleware.Upload, middleware.File).Then(handlers.AddFile)).Methods("POST")
112+
router.Handle("/stream/{uploadID}/{fileID}/{filename}", authChain.Append(middleware.Upload, middleware.File).Then(handlers.RemoveFile)).Methods("DELETE")
113+
router.Handle("/stream/{uploadID}/{fileID}/{filename}", authChainWithRedirect.AppendChain(getFileChain).Then(handlers.GetFile)).Methods("HEAD", "GET")
114+
router.Handle("/stream/{uploadID}/{fileID}/{filename}/yubikey/{yubikey}", authChainWithRedirect.AppendChain(getFileChain).Then(handlers.GetFile)).Methods("HEAD", "GET")
115+
router.Handle("/archive/{uploadID}/{filename}", authChainWithRedirect.Append(middleware.Upload, middleware.Yubikey).Then(handlers.GetArchive)).Methods("HEAD", "GET")
116+
router.Handle("/archive/{uploadID}/{filename}/yubikey/{yubikey}", authChainWithRedirect.Append(middleware.Upload, middleware.Yubikey).Then(handlers.GetArchive)).Methods("HEAD", "GET")
117+
router.Handle("/auth/google/login", authChain.Then(handlers.GoogleLogin)).Methods("GET")
118+
router.Handle("/auth/google/callback", stdChainWithRedirect.Then(handlers.GoogleCallback)).Methods("GET")
119+
router.Handle("/auth/ovh/login", authChain.Then(handlers.OvhLogin)).Methods("GET")
120+
router.Handle("/auth/ovh/callback", stdChainWithRedirect.Then(handlers.OvhCallback)).Methods("GET")
121+
router.Handle("/auth/logout", authChain.Then(handlers.Logout)).Methods("GET")
122+
router.Handle("/me", authChain.Then(handlers.UserInfo)).Methods("GET")
123+
router.Handle("/me", authChain.Then(handlers.DeleteAccount)).Methods("DELETE")
124+
router.Handle("/me/token", authChain.Then(handlers.CreateToken)).Methods("POST")
125+
router.Handle("/me/token/{token}", authChain.Then(handlers.RevokeToken)).Methods("DELETE")
126+
router.Handle("/me/uploads", authChain.Then(handlers.GetUserUploads)).Methods("GET")
127+
router.Handle("/me/uploads", authChain.Then(handlers.RemoveUserUploads)).Methods("DELETE")
128+
router.Handle("/qrcode", stdChain.Then(handlers.GetQrCode)).Methods("GET")
129+
router.PathPrefix("/clients/").Handler(http.StripPrefix("/clients/", http.FileServer(http.Dir("../clients"))))
130+
router.PathPrefix("/changelog/").Handler(http.StripPrefix("/changelog/", http.FileServer(http.Dir("../changelog"))))
131+
router.PathPrefix("/").Handler(http.FileServer(http.Dir("./public/")))
132+
133+
handler := common.StripPrefix(common.Config.Path, router)
134+
135+
http.Handle("/", handler)
133136

134137
go UploadsCleaningRoutine()
135138

@@ -150,10 +153,10 @@ func main() {
150153
}
151154

152155
tlsConfig := &tls.Config{MinVersion: tls.VersionTLS10, Certificates: []tls.Certificate{cert}}
153-
server = &http.Server{Addr: address, Handler: r, TLSConfig: tlsConfig}
156+
server = &http.Server{Addr: address, Handler: handler, TLSConfig: tlsConfig}
154157
} else {
155158
proto = "http"
156-
server = &http.Server{Addr: address, Handler: r}
159+
server = &http.Server{Addr: address, Handler: handler}
157160
}
158161

159162
log.Infof("Starting http server at %s://%s", proto, address)

server/plikd.cfg

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
#
99

1010
LogLevel = "INFO" # Other levels : DEBUG, INFO, WARNING, CRITICAL, FATAL
11-
ListenPort = 8080
12-
ListenAddress = "0.0.0.0"
11+
12+
ListenPort = 8080 # Port the HTTP server will listen on
13+
ListenAddress = "0.0.0.0" # Address the HTTP server will bind on
14+
Path = "/" # Root path for the HTTP server
15+
1316
MaxFileSize = 10737418240 # 10GB
1417
MaxFilePerUpload = 1000
1518

@@ -29,8 +32,10 @@ SourceIpHeader = "" # If behind reverse proxy (expl: X-FORWARDED
2932
UploadWhitelist = [] # Restrict upload to one or more ip range
3033

3134
Authentication = false # Enable authentication
35+
NoAnonymousUploads = false
3236
GoogleApiClientID = "" # Google api client ID
3337
GoogleApiSecret = "" # Google api client secret
38+
GoogleValidDomains = [] # List of acceptable email domains for users
3439
OvhApiKey = "" # OVH api application key
3540
OvhApiSecret = "" # OVH api application secret
3641

0 commit comments

Comments
 (0)