From 7e1391111797f9ca63ac2fa48f2267b9cbbcafcf Mon Sep 17 00:00:00 2001 From: rarenivar Date: Tue, 9 Feb 2016 22:38:19 -0700 Subject: [PATCH 01/81] Initial commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000000..ca8cc61ff2 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# project5799 +Term project for Cloud Computing 5799 From 8ca4ea4b71f9144d109631c7b051f2c5c1919fb0 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 5 Mar 2016 12:11:27 -0700 Subject: [PATCH 02/81] Initial webfront.go from google --- server/webfront/webfront.go | 232 ++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 server/webfront/webfront.go diff --git a/server/webfront/webfront.go b/server/webfront/webfront.go new file mode 100644 index 0000000000..023748714b --- /dev/null +++ b/server/webfront/webfront.go @@ -0,0 +1,232 @@ +// Started with https://github.com/nf/webfront/blob/master/main.go +// by Andrew Gerrand + +/* +Copyright 2011 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +webfront is an HTTP server and reverse proxy. + +It reads a JSON-formatted rule file like this: + + [ + {"Host": "example.com", "Serve": "/var/www"}, + {"Host": "example.org", "Forward": "localhost:8080"} + ] + +For all requests to the host example.com (or any name ending in +".example.com") it serves files from the /var/www directory. + +For requests to example.org, it forwards the request to the HTTP +server listening on localhost port 8080. + +Usage of webfront: + -http=":80": HTTP listen address + -https="": HTTPS listen address (leave empty to disable) + -https_cert="": HTTPS certificate file + -https_key="": HTTPS key file + -poll=10s: file poll interval + -rules="": rule definition file + +webfront was written by Andrew Gerrand +*/ +package main + +import ( + "crypto/tls" + "encoding/json" + "flag" + "log" + "net" + "net/http" + "net/http/httputil" + "os" + "strconv" + "strings" + "sync" + "time" +) + +var ( + httpAddr = flag.String("http", ":80", "HTTP listen address") + httpsAddr = flag.String("https", "", "HTTPS listen address (leave empty to disable)") + certFile = flag.String("https_cert", "", "HTTPS certificate file") + keyFile = flag.String("https_key", "", "HTTPS key file") + ruleFile = flag.String("rules", "", "rule definition file") + pollInterval = flag.Duration("poll", time.Second*10, "file poll interval") +) + +func main() { + flag.Parse() + s, err := NewServer(*ruleFile, *pollInterval) + if err != nil { + log.Fatal(err) + } + httpFD, _ := strconv.Atoi(os.Getenv("RUNSIT_PORTFD_http")) + httpsFD, _ := strconv.Atoi(os.Getenv("RUNSIT_PORTFD_https")) + if httpsFD >= 3 || *httpsAddr != "" { + cert, err := tls.LoadX509KeyPair(*certFile, *keyFile) + if err != nil { + log.Fatal(err) + } + c := &tls.Config{Certificates: []tls.Certificate{cert}} + l := tls.NewListener(listen(httpsFD, *httpsAddr), c) + go func() { + log.Fatal(http.Serve(l, s)) + }() + } + log.Fatal(http.Serve(listen(httpFD, *httpAddr), s)) +} + +func listen(fd int, addr string) net.Listener { + var l net.Listener + var err error + if fd >= 3 { + l, err = net.FileListener(os.NewFile(uintptr(fd), "http")) + } else { + l, err = net.Listen("tcp", addr) + } + if err != nil { + log.Fatal(err) + } + return l +} + +// Server implements an http.Handler that acts as either a reverse proxy or +// a simple file server, as determined by a rule set. +type Server struct { + mu sync.RWMutex // guards the fields below + last time.Time + rules []*Rule +} + +// Rule represents a rule in a configuration file. +type Rule struct { + Host string // to match against request Host header + Forward string // non-empty if reverse proxy + Serve string // non-empty if file server + + handler http.Handler +} + +// NewServer constructs a Server that reads rules from file with a period +// specified by poll. +func NewServer(file string, poll time.Duration) (*Server, error) { + s := new(Server) + if err := s.loadRules(file); err != nil { + return nil, err + } + go s.refreshRules(file, poll) + return s, nil +} + +// ServeHTTP matches the Request with a Rule and, if found, serves the +// request with the Rule's handler. +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if h := s.handler(r); h != nil { + h.ServeHTTP(w, r) + return + } + http.Error(w, "Not found.", http.StatusNotFound) +} + +// handler returns the appropriate Handler for the given Request, +// or nil if none found. +func (s *Server) handler(req *http.Request) http.Handler { + s.mu.RLock() + defer s.mu.RUnlock() + h := req.Host + // Some clients include a port in the request host; strip it. + if i := strings.Index(h, ":"); i >= 0 { + h = h[:i] + } + for _, r := range s.rules { + if h == r.Host || strings.HasSuffix(h, "."+r.Host) { + return r.handler + } + } + return nil +} + +// refreshRules polls file periodically and refreshes the Server's rule +// set if the file has been modified. +func (s *Server) refreshRules(file string, poll time.Duration) { + for { + if err := s.loadRules(file); err != nil { + log.Println(err) + } + time.Sleep(poll) + } +} + +// loadRules tests whether file has been modified since its last invocation +// and, if so, loads the rule set from file. +func (s *Server) loadRules(file string) error { + fi, err := os.Stat(file) + if err != nil { + return err + } + mtime := fi.ModTime() + if !mtime.After(s.last) && s.rules != nil { + return nil // no change + } + rules, err := parseRules(file) + if err != nil { + return err + } + s.mu.Lock() + s.last = mtime + s.rules = rules + s.mu.Unlock() + return nil +} + +// parseRules reads rule definitions from file, constructs the Rule handlers, +// and returns the resultant Rules. +func parseRules(file string) ([]*Rule, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + defer f.Close() + var rules []*Rule + if err := json.NewDecoder(f).Decode(&rules); err != nil { + return nil, err + } + for _, r := range rules { + r.handler = makeHandler(r) + if r.handler == nil { + log.Printf("bad rule: %#v", r) + } + } + return rules, nil +} + +// makeHandler constructs the appropriate Handler for the given Rule. +func makeHandler(r *Rule) http.Handler { + if h := r.Forward; h != "" { + return &httputil.ReverseProxy{ + Director: func(req *http.Request) { + req.URL.Scheme = "http" + req.URL.Host = h + }, + } + } + if d := r.Serve; d != "" { + return http.FileServer(http.Dir(d)) + } + return nil +} From 4843c92e2bf6f4c6c6b80750d06e0d9d3557ff0d Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 5 Mar 2016 13:35:58 -0700 Subject: [PATCH 03/81] starting to do something - routing only still no JWTs yet --- server/webfront/.gitignore | 2 ++ server/webfront/rules.json | 10 ++++++++++ server/webfront/simpleserver.go | 19 +++++++++++++++++++ server/webfront/startfakes.sh | 7 +++++++ server/webfront/webfront.go | 19 ++++++++++++------- 5 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 server/webfront/.gitignore create mode 100644 server/webfront/rules.json create mode 100644 server/webfront/simpleserver.go create mode 100644 server/webfront/startfakes.sh diff --git a/server/webfront/.gitignore b/server/webfront/.gitignore new file mode 100644 index 0000000000..0550bc82f0 --- /dev/null +++ b/server/webfront/.gitignore @@ -0,0 +1,2 @@ +server.pem +server.key \ No newline at end of file diff --git a/server/webfront/rules.json b/server/webfront/rules.json new file mode 100644 index 0000000000..36eaf9abcd --- /dev/null +++ b/server/webfront/rules.json @@ -0,0 +1,10 @@ + [ + {"Host": "local.com", "Path" : "/8001", "Forward": "localhost:8001"}, + {"Host": "local.com", "Path" : "/8002", "Forward": "localhost:8002"}, + {"Host": "local.com", "Path" : "/8003", "Forward": "localhost:8003"}, + {"Host": "local.com", "Path" : "/8004", "Forward": "localhost:8004"}, + {"Host": "local.com", "Path" : "/8005", "Forward": "localhost:8005"}, + {"Host": "local.com", "Path" : "/8006", "Forward": "localhost:8006"}, + {"Host": "local.com", "Path" : "/8007", "Forward": "localhost:8007"} + ] + diff --git a/server/webfront/simpleserver.go b/server/webfront/simpleserver.go new file mode 100644 index 0000000000..b70b04a019 --- /dev/null +++ b/server/webfront/simpleserver.go @@ -0,0 +1,19 @@ +package main + +import ( + "io" + "net/http" + "os" +) + +func hello(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "Hitting "+os.Args[1]+" with "+r.URL.Path+"\n") +} + +func main() { + http.HandleFunc("/", hello) + // Make sure you have the server.pem and server.key file. To gen self signed: + // openssl genrsa -out server.key 2048 + // openssl req -new -x509 -key server.key -out server.pem -days 3650 + http.ListenAndServeTLS(":"+os.Args[1], "server.pem", "server.key", nil) +} diff --git a/server/webfront/startfakes.sh b/server/webfront/startfakes.sh new file mode 100644 index 0000000000..1b96d5c3f1 --- /dev/null +++ b/server/webfront/startfakes.sh @@ -0,0 +1,7 @@ +go run simpleserver.go 8001 & +go run simpleserver.go 8002 & +go run simpleserver.go 8003 & +go run simpleserver.go 8004 & +go run simpleserver.go 8005 & +go run simpleserver.go 8006 & +go run simpleserver.go 8007 diff --git a/server/webfront/webfront.go b/server/webfront/webfront.go index 023748714b..f6d53f9875 100644 --- a/server/webfront/webfront.go +++ b/server/webfront/webfront.go @@ -75,6 +75,8 @@ func main() { if err != nil { log.Fatal(err) } + // overrided the default so we can use self-signed certs on our microservices + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} httpFD, _ := strconv.Atoi(os.Getenv("RUNSIT_PORTFD_http")) httpsFD, _ := strconv.Atoi(os.Getenv("RUNSIT_PORTFD_https")) if httpsFD >= 3 || *httpsAddr != "" { @@ -116,8 +118,9 @@ type Server struct { // Rule represents a rule in a configuration file. type Rule struct { Host string // to match against request Host header - Forward string // non-empty if reverse proxy - Serve string // non-empty if file server + Path string // to match against a path (start) + Forward string // reverse proxy map-to + // Serve string // non-empty if file server handler http.Handler } @@ -149,15 +152,18 @@ func (s *Server) handler(req *http.Request) http.Handler { s.mu.RLock() defer s.mu.RUnlock() h := req.Host + p := req.URL.Path // Some clients include a port in the request host; strip it. if i := strings.Index(h, ":"); i >= 0 { h = h[:i] } for _, r := range s.rules { - if h == r.Host || strings.HasSuffix(h, "."+r.Host) { + log.Println(p, "==", r.Path) + if strings.HasPrefix(p, r.Path) { return r.handler } } + log.Println("returning nil") return nil } @@ -220,13 +226,12 @@ func makeHandler(r *Rule) http.Handler { if h := r.Forward; h != "" { return &httputil.ReverseProxy{ Director: func(req *http.Request) { - req.URL.Scheme = "http" + req.URL.Scheme = "https" req.URL.Host = h + req.URL.Path = "/boo1" // TODO JvD - regex to change path here + // Todo JvD Auth check goes here?? }, } } - if d := r.Serve; d != "" { - return http.FileServer(http.Dir(d)) - } return nil } From 5d39f7d513392472eff04e62ca0a8cd998f5880a Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 5 Mar 2016 14:18:15 -0700 Subject: [PATCH 04/81] move to TLS, cleanup --- server/webfront/webfront.go | 85 ++++--------------------------------- 1 file changed, 8 insertions(+), 77 deletions(-) diff --git a/server/webfront/webfront.go b/server/webfront/webfront.go index f6d53f9875..769a3e8a89 100644 --- a/server/webfront/webfront.go +++ b/server/webfront/webfront.go @@ -1,48 +1,6 @@ // Started with https://github.com/nf/webfront/blob/master/main.go // by Andrew Gerrand -/* -Copyright 2011 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* -webfront is an HTTP server and reverse proxy. - -It reads a JSON-formatted rule file like this: - - [ - {"Host": "example.com", "Serve": "/var/www"}, - {"Host": "example.org", "Forward": "localhost:8080"} - ] - -For all requests to the host example.com (or any name ending in -".example.com") it serves files from the /var/www directory. - -For requests to example.org, it forwards the request to the HTTP -server listening on localhost port 8080. - -Usage of webfront: - -http=":80": HTTP listen address - -https="": HTTPS listen address (leave empty to disable) - -https_cert="": HTTPS certificate file - -https_key="": HTTPS key file - -poll=10s: file poll interval - -rules="": rule definition file - -webfront was written by Andrew Gerrand -*/ package main import ( @@ -50,18 +8,17 @@ import ( "encoding/json" "flag" "log" - "net" + // "net" "net/http" "net/http/httputil" "os" - "strconv" + // "strconv" "strings" "sync" "time" ) var ( - httpAddr = flag.String("http", ":80", "HTTP listen address") httpsAddr = flag.String("https", "", "HTTPS listen address (leave empty to disable)") certFile = flag.String("https_cert", "", "HTTPS certificate file") keyFile = flag.String("https_key", "", "HTTPS key file") @@ -75,40 +32,14 @@ func main() { if err != nil { log.Fatal(err) } - // overrided the default so we can use self-signed certs on our microservices + + // override the default so we can use self-signed certs on our microservices http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - httpFD, _ := strconv.Atoi(os.Getenv("RUNSIT_PORTFD_http")) - httpsFD, _ := strconv.Atoi(os.Getenv("RUNSIT_PORTFD_https")) - if httpsFD >= 3 || *httpsAddr != "" { - cert, err := tls.LoadX509KeyPair(*certFile, *keyFile) - if err != nil { - log.Fatal(err) - } - c := &tls.Config{Certificates: []tls.Certificate{cert}} - l := tls.NewListener(listen(httpsFD, *httpsAddr), c) - go func() { - log.Fatal(http.Serve(l, s)) - }() - } - log.Fatal(http.Serve(listen(httpFD, *httpAddr), s)) -} -func listen(fd int, addr string) net.Listener { - var l net.Listener - var err error - if fd >= 3 { - l, err = net.FileListener(os.NewFile(uintptr(fd), "http")) - } else { - l, err = net.Listen("tcp", addr) - } - if err != nil { - log.Fatal(err) - } - return l + http.ListenAndServeTLS(*httpsAddr, *certFile, *keyFile, s) } -// Server implements an http.Handler that acts as either a reverse proxy or -// a simple file server, as determined by a rule set. +// Server implements an http.Handler that acts asither a reverse proxy type Server struct { mu sync.RWMutex // guards the fields below last time.Time @@ -158,8 +89,9 @@ func (s *Server) handler(req *http.Request) http.Handler { h = h[:i] } for _, r := range s.rules { - log.Println(p, "==", r.Path) + // log.Println(p, "==", r.Path) if strings.HasPrefix(p, r.Path) { + // Todo JvD Auth check goes here?? return r.handler } } @@ -229,7 +161,6 @@ func makeHandler(r *Rule) http.Handler { req.URL.Scheme = "https" req.URL.Host = h req.URL.Path = "/boo1" // TODO JvD - regex to change path here - // Todo JvD Auth check goes here?? }, } } From 20e8a8eb8b63f15df8f0b3057d8d90d0af568a0e Mon Sep 17 00:00:00 2001 From: Michael Albers Date: Tue, 15 Mar 2016 20:47:40 -0600 Subject: [PATCH 05/81] Initial check in of CameraFeed (recording live stream) microservice. --- CameraFeed/.gitignore | 1 + CameraFeed/AmcrestIPM-721S.js | 3 + CameraFeed/CameraFeed.js | 144 +++++++++++++++++++++++ CameraFeed/CameraFeedError.js | 51 ++++++++ CameraFeed/CameraFeed_v1.js | 146 +++++++++++++++++++++++ CameraFeed/Dockerfile | 33 ++++++ CameraFeed/keys/cert.pem | 22 ++++ CameraFeed/keys/key.pem | 28 +++++ CameraFeed/package.json | 11 ++ CameraFeed/src/CommandLine.cpp | 115 ++++++++++++++++++ CameraFeed/src/CommandLine.h | 23 ++++ CameraFeed/src/CurlInit.cpp | 23 ++++ CameraFeed/src/CurlInit.h | 22 ++++ CameraFeed/src/GetVideo.cpp | 186 ++++++++++++++++++++++++++++++ CameraFeed/src/GetVideo.h | 64 ++++++++++ CameraFeed/src/JPEGFileWriter.cpp | 33 ++++++ CameraFeed/src/JPEGFileWriter.h | 22 ++++ CameraFeed/src/JPEGHandler.h | 9 ++ CameraFeed/src/Makefile | 54 +++++++++ CameraFeed/src/main.cpp | 29 +++++ 20 files changed, 1019 insertions(+) create mode 100644 CameraFeed/.gitignore create mode 100644 CameraFeed/AmcrestIPM-721S.js create mode 100644 CameraFeed/CameraFeed.js create mode 100644 CameraFeed/CameraFeedError.js create mode 100644 CameraFeed/CameraFeed_v1.js create mode 100644 CameraFeed/Dockerfile create mode 100644 CameraFeed/keys/cert.pem create mode 100755 CameraFeed/keys/key.pem create mode 100644 CameraFeed/package.json create mode 100644 CameraFeed/src/CommandLine.cpp create mode 100644 CameraFeed/src/CommandLine.h create mode 100644 CameraFeed/src/CurlInit.cpp create mode 100644 CameraFeed/src/CurlInit.h create mode 100644 CameraFeed/src/GetVideo.cpp create mode 100644 CameraFeed/src/GetVideo.h create mode 100644 CameraFeed/src/JPEGFileWriter.cpp create mode 100644 CameraFeed/src/JPEGFileWriter.h create mode 100644 CameraFeed/src/JPEGHandler.h create mode 100644 CameraFeed/src/Makefile create mode 100644 CameraFeed/src/main.cpp diff --git a/CameraFeed/.gitignore b/CameraFeed/.gitignore new file mode 100644 index 0000000000..e4e5f6c8b2 --- /dev/null +++ b/CameraFeed/.gitignore @@ -0,0 +1 @@ +*~ \ No newline at end of file diff --git a/CameraFeed/AmcrestIPM-721S.js b/CameraFeed/AmcrestIPM-721S.js new file mode 100644 index 0000000000..1d3d2bcc9c --- /dev/null +++ b/CameraFeed/AmcrestIPM-721S.js @@ -0,0 +1,3 @@ +// Used to read initial data from camera (image size, get channels, etc.), then +// launch libcurl application to do actual feed reading. + diff --git a/CameraFeed/CameraFeed.js b/CameraFeed/CameraFeed.js new file mode 100644 index 0000000000..0637622873 --- /dev/null +++ b/CameraFeed/CameraFeed.js @@ -0,0 +1,144 @@ +/* + * @author Michael Albers + * For CSCI 5799 + */ + +"use strict"; + +var https = require("https"); +var url = require("url"); +const fs = require('fs'); +var CameraFeedError = require('./CameraFeedError.js'); + +// TODO: remember to allow security exception until better certs are had + +// Model URI: +// https://localhost:8080/CameraFeed/v1? + +/** Debug flag for development*/ +var debugFlag = true; + +/** HTTP server listen port */ +const PORT = 8080; + +/** REST resources */ +const CAMERA_RESOURCE = "CameraFeed" + +/** Name of the server for HTTP header */ +const SERVER = "CameraFeed/0.1" + +/* + * API versions + */ +var CameraFeed_v1 = require('./CameraFeed_v1.js'); +var apiMap = { + 'v1' : CameraFeed_v1 +}; + +/* + * Given the pathname of a URI (i.e., /CameraFeed/v1) verifies that + * the resource is valid and returns the API version. + * + * @param path URI pathname + * @return API version string + * @throws CameraFeedError on invalid pathname or resource + */ +var verifyResourceVersion = function(path) { + var paths = path.split("/"); + if (paths.length != 3) + { + var returnJSON = CameraFeedError.buildJSON( + CameraFeedError.InvalidPath, + 'Resource path did not contain correct number of parts. Must be /' + + CAMERA_RESOURCE + '/'); + throw new CameraFeedError.CameraFeedError( + "Invalid Resource", 400, returnJSON); + } + + var resource = paths[1]; + var apiVersion = paths[2]; + + if (CAMERA_RESOURCE.localeCompare(resource) != 0) + { + var returnJSON = CameraFeedError.buildJSON( + CameraFeedError.InvalidPath, + 'Invalid resource. Must be ' + CAMERA_RESOURCE); + throw new CameraFeedError.CameraFeedError( + "Invalid Resource", 400, returnJSON); + } + + return apiVersion; +} + +/** + * Request event handler for HttpsServer object. + * See https://nodejs.org/api/http.html#http_event_request + */ +var requestHandler = function(request, response) +{ + var parsedURL = url.parse(request.url, true); + + if (debugFlag) { + console.log("===Request==="); + console.log("Date: " + new Date().toJSON()); + console.log("URL: " + parsedURL.pathname); + console.log("Method: " + request.method); // GET, POST, etc. + console.log("Headers: " + JSON.stringify(request.headers)); + console.log("Query: " + JSON.stringify(parsedURL.query)); + console.log("===END==="); + } + + try + { + var apiVersion = verifyResourceVersion(parsedURL.pathname); + + if (debugFlag) { + console.log("--Client requested API version: " + apiVersion); + } + + if (apiMap[apiVersion]) + { + apiMap[apiVersion].setDebug(debugFlag); + apiMap[apiVersion].processRequest(parsedURL.query, response); + } + else + { + var returnJSON = CameraFeedError.buildJSON( + CameraFeedError.InvalidPath, + 'Invalid API version requested: ' + apiVersion + '. Must be one of: ' + + Object.keys(apiMap)); + throw new CameraFeedError.CameraFeedError( + "Invalid API version", 400, returnJSON); + } + } + catch (e) + { + // This helps in development when some other exception besides CameraFeedError + // might be getting thrown. + if (debugFlag) { + console.log(e.name); + console.log(e.stack); + } + + response.writeHead(e.getHttpCode(), { + 'Content-Type': 'application/JSON', + 'Server': SERVER + }); + response.write(JSON.stringify(e.getJSON())); + } + response.end(); +} + +if (debugFlag) { + console.log(new Date().toJSON()); + console.log("Starting CameraFeed microservice..."); +} + +const options = { + key: fs.readFileSync('keys/key.pem'), + cert: fs.readFileSync('keys/cert.pem') +}; + +https.createServer(options, requestHandler).listen(PORT); + +// TODO: next need to register with API gateway diff --git a/CameraFeed/CameraFeedError.js b/CameraFeed/CameraFeedError.js new file mode 100644 index 0000000000..90781695bc --- /dev/null +++ b/CameraFeed/CameraFeedError.js @@ -0,0 +1,51 @@ +/* + * @author Michael Albers + * For CSCI 5799 + */ + +"use strict"; + +/* + * Error codes + */ +exports.InvalidPath = 100; +exports.InvalidResource = 101; +exports.InvalidAPI = 102; +exports.InvalidAction = 103; +exports.InvalidCameraId = 104; + +/* + * Builds the JSON object for CameraFeedError + * + * @param errorCode One of the error codes listed above. + * @param message String describing the error + * @return object for CameraFeedError + */ +function buildJSON(errorCode, message) { + return { + "code": errorCode, + "message": message + } +} +exports.buildJSON = buildJSON; + +/* + * General purpose error class for CameraFeed microservice. + */ +class CameraFeedError extends Error { + constructor(name, httpCode, returnJSON) { + super(name); + this.httpCode = httpCode; + this.returnJSON = returnJSON; + } + + getHttpCode() { + return this.httpCode; + } + + getJSON() { + return this.returnJSON; + } +} + +exports.CameraFeedError = CameraFeedError; diff --git a/CameraFeed/CameraFeed_v1.js b/CameraFeed/CameraFeed_v1.js new file mode 100644 index 0000000000..c5352d1106 --- /dev/null +++ b/CameraFeed/CameraFeed_v1.js @@ -0,0 +1,146 @@ +/* + * @author Michael Albers + * For CSCI 5799 + */ + +"use strict"; + +// Model URI: +// https://localhost:8080/CameraFeed/v1?action=[start|stop]&camera_id=[id] + +var path = require('path'); +var CameraFeedError = require('./CameraFeedError.js'); + +var moduleName = path.basename(module.id, ".js") + +var debugFlag = false; + +/* action values*/ +const START = "start"; +const STOP = "stop"; + +/** + * Sets the debug flag for this module. + * @param theDebugFlag debug value (true/false) + */ +function setDebug(theDebugFlag) { + debugFlag = theDebugFlag; +} +exports.setDebug = setDebug; + +/** + * Pulls the action parameter from the query, verifying it is correct. + * + * @param query HTTP query object + * @return action string + * @throws CameraFeedError on any problem with the action parameter + */ +function getAction(query) { + const actionKey = "action"; + if (query[actionKey]) + { + var action = query[actionKey]; + if (START.localeCompare(action) != 0 && + STOP.localeCompare(action) != 0) + { + var returnJSON = CameraFeedError.buildJSON( + CameraFeedError.InvalidAction, + 'Invalid "action" parameter provided. Must be either "' + START + + '" or "' + STOP + '".'); + throw new CameraFeedError.CameraFeedError( + "Invalid Action Provided", 400, returnJSON); + } + } + else + { + var returnJSON = CameraFeedError.buildJSON( + CameraFeedError.InvalidAction, + 'No "action" parameter provided.'); + throw new CameraFeedError.CameraFeedError( + "No Action Provided", 400, returnJSON); + } + + return query[actionKey]; +} + +/** + * Pulls the camera_id parameter from the query, verifying it is correct. + * + * @param query HTTP query object + * @return camera id + * @throws CameraFeedError on any problem with the camera_id parameter + */ +function getCameraId(query) { + const cameraIdKey = "camera_id"; + if (query[cameraIdKey]) + { + var cameraId = query[cameraIdKey]; + // TODO: need to determine the id format and verify it here + // { + // var returnJSON = CameraFeedError.buildJSON( + // CameraFeedError.InvalidCameraId, + // 'Invalid "cameraId" parameter provided. Must be either "' + START + '" or "' + + // STOP + '".'); + // throw new CameraFeedError.CameraFeedError( + // "Invalid CameraId Provided", 400, returnJSON); + // } + + // TODO: then query DB to see if this is OK + } + else + { + var returnJSON = CameraFeedError.buildJSON( + CameraFeedError.InvalidCameraId, + 'No "camera_id" parameter provided.'); + throw new CameraFeedError.CameraFeedError( + "No Camera_id Provided", 400, returnJSON); + } + + return query[cameraIdKey]; +} + +function executeStart(cameraId) { + // TODO: + // - check if camera already streaming + // if so, success + // if not, spawn child process to retrieve feed (C++, libcurl) + // update DB to say feed is started + // (need to errors from child process) +} + +function executeStop(cameraId) { +} + +/** + * Processes the user's REST query for starting/stopping a camera feed. + * @param query HTTP query object (see node.js URL.parse) + * @param response HTTP response object + * @throws CameraFeedError on any problem + */ +function processRequest(query, response) { + if (debugFlag) { + console.log("--" + moduleName + + " Processing query:\n " + JSON.stringify(query)); + } + + var action = getAction(query); + var cameraId = getCameraId(query); + + if (debugFlag) { + console.log("--Processed parameters:"); + console.log(" Action: " + action); + console.log(" cameraId: " + cameraId); + } + + if (START.localeCompare(action) == 0) { + executeStart(cameraId); + } + else { + executeStop(cameraId); + } + + response.writeHead(200, { + 'Server': moduleName + }); +} +exports.processRequest = processRequest; diff --git a/CameraFeed/Dockerfile b/CameraFeed/Dockerfile new file mode 100644 index 0000000000..be7974501e --- /dev/null +++ b/CameraFeed/Dockerfile @@ -0,0 +1,33 @@ +# Docker file for CameraFeed microservice + +FROM debian + +# Need to run this to see what's in the various sources (need this to get +# curl). +RUN apt-get -qq update + +# Get g++, make +RUN apt-get install -y build-essential + +# Packages for node.js service code +RUN apt-get install -y curl +RUN curl -sL https://deb.nodesource.com/setup_5.x | /bin/bash - +RUN apt-get install -y nodejs + +# Packages used by C++ code to read camera stream +RUN apt-get install -y libcurl3 libcurl4-gnutls-dev + +# Brind in CameraFeed source +RUN mkdir /usr/src/CameraFeed +WORKDIR /usr/src/CameraFeed +# Manual copy, excludes Dockerfile, emacs tmp files +COPY *js . +COPY keys . +COPY src . +COPY *json . + +RUN npm install + +# Build C++ code +WORKDIR src +RUN make diff --git a/CameraFeed/keys/cert.pem b/CameraFeed/keys/cert.pem new file mode 100644 index 0000000000..b0aaf22b5f --- /dev/null +++ b/CameraFeed/keys/cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrTCCApWgAwIBAgIJAOKKLis9KpfsMA0GCSqGSIb3DQEBCwUAMG0xCzAJBgNV +BAYTAlVTMREwDwYDVQQIDAhDb2xvcmFkbzEPMA0GA1UEBwwGRGVudmVyMQwwCgYD +VQQKDANNUkoxFzAVBgNVBAMMDk1pY2hhZWwgQWxiZXJzMRMwEQYJKoZIhvcNAQkB +FgRub25lMB4XDTE2MDMwMTA0MTcyNVoXDTE3MDcxNDA0MTcyNVowbTELMAkGA1UE +BhMCVVMxETAPBgNVBAgMCENvbG9yYWRvMQ8wDQYDVQQHDAZEZW52ZXIxDDAKBgNV +BAoMA01SSjEXMBUGA1UEAwwOTWljaGFlbCBBbGJlcnMxEzARBgkqhkiG9w0BCQEW +BG5vbmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWq7UF6h6IeWtp +37Hzddfgh/B7ZHqv3jBem8yGhgTvj0rwAZ28XPwi9iQ8o6/8OFGVfZ/48WDi91xU +qUQwtf+ivT6Sp6zMlm3X1PKpRacQSJSODw4jZEV3Im1/dg5tXhxc/gHmfJaIhOEK +5l2VYgzwxND++tJx2REEXd/5rKo6LJdYfTXmavVKblx6yTmwu0hUvPsna4Th0kJU +tMLXoM/bB95G6fUu6BxUx8oI7Pj8UIfKLJwEGhvc0ZOo6/oLESoezSu8mgLOyu1n +iXZQzmjpdaGWhs5xT5kZgzPu/vkYyQ8jWTtbXyTfSlefRFaiNT0CZzzsBbKVlHeS +h1I6kcsFAgMBAAGjUDBOMB0GA1UdDgQWBBRpFtDA1wCZDPPs+i8KjSxFZTwjzDAf +BgNVHSMEGDAWgBRpFtDA1wCZDPPs+i8KjSxFZTwjzDAMBgNVHRMEBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4IBAQAvoxhDOVBZUeBT/xJOdLAokXicWBP1QtTGuZxLf5VR +Y4BkcgpDuo/UQjDRN+kDWLZwypn47EOhacbLBazv+6La7TJDnH/gQqWQOqyYqbyL +RON8heFWnbMy3uMrNHrDEi2mf2SU+hmPSNWLM5S+4NdgB0xVjADJE0ugvebrMMr7 +7tUk0fPhYg0yi+S3eFdpJkfV7NURrOjpXMyQLwCt1q4w8hEoGFYnx22BQWwVE3Q6 +FV1a5e1xvwgaDluQ8bnciueQzm76C7FgTp0qbUdfWfiMzs0ziMkzUkh87Jbdwq0v +e3c7IGKfHt1xZVbR6JUbK/wKGPUTQdiwAiYVdfowBl0D +-----END CERTIFICATE----- diff --git a/CameraFeed/keys/key.pem b/CameraFeed/keys/key.pem new file mode 100755 index 0000000000..de2e3921df --- /dev/null +++ b/CameraFeed/keys/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCWq7UF6h6IeWtp +37Hzddfgh/B7ZHqv3jBem8yGhgTvj0rwAZ28XPwi9iQ8o6/8OFGVfZ/48WDi91xU +qUQwtf+ivT6Sp6zMlm3X1PKpRacQSJSODw4jZEV3Im1/dg5tXhxc/gHmfJaIhOEK +5l2VYgzwxND++tJx2REEXd/5rKo6LJdYfTXmavVKblx6yTmwu0hUvPsna4Th0kJU +tMLXoM/bB95G6fUu6BxUx8oI7Pj8UIfKLJwEGhvc0ZOo6/oLESoezSu8mgLOyu1n +iXZQzmjpdaGWhs5xT5kZgzPu/vkYyQ8jWTtbXyTfSlefRFaiNT0CZzzsBbKVlHeS +h1I6kcsFAgMBAAECggEBAJVzMBXzyeF4/pB/8FUbeMwgSus6GW/EppnRVCfDW7X7 +nks+byVd3kMXf44elvtJKbNsbndRhdbboVvgoeDnRfA4Yo65fu9X7xB9C03X5wSB +2cinKlD0ruqi3ZXmlhzpkpyy31OuFOrJUyeqpPz9yvQvZVblmESRGQ4Jx5YyLI+S +EcpzUKvv2H/t7ykqv33JaDJZ5KabtJzezbSnao0GDJHKsEaZVPXgS2Je1vxAw4GX +UdPqdISuDPM2vw6aEew8AmH0CGJPB1Goo7QaVrT+sgAi6SAMZDukSrbR5g68p5At +Fhi+qDDfdrZqR5sQ9hCmTep4hPr8DKZmp6dZGmNw6ukCgYEAx42JPBsUgPXPMUkW +PualQM9onhcoftZDKauErDagN9ocXFaFfPpSo8SVCYOfpY9pCPGbmAAhZj7mlh0S +tCNk5G1lNLiEgbegZTkKGzdqbCa7ThyUZ3dx5imw8NDuaJ5bYgls7uYRThz8DIJR +9jkO+sSMXGnIbnyeUslXBgJ2SeMCgYEAwUpovmso0WmOErXoyYeRav2BEp+lhL1H +nhn+gsh5tyuanmgKkofgqPe+51KKfEkkogAcmpKJS1KXWBn6ZfW7cn4xhfeycmIH +kGzSWBnZiSB/jIcaHuyp1zfSty22PzFPpqrFbQs8l3dkNHSxeOhBCRzOBG8coujN +IdMN92f5S/cCgYAB/QIKDEcHBev7lLvZAplQ7QAg2yA3K1Fd/+yBfsXX6J9xuBb6 +aNAb+6B0iNA1aRll0mp3eEDc8PGBO2btTpD5ybFBdjkzxa2edJQKM2InE7e4DobY +BROodG/j5mEJv9IvRuLD+pzfh2Bni4DfkC/7BaxUW2V43FsDfigU0j91ewKBgEsv ++6CepIkZK0fB9SR3lKxuogexjDwfOL2aVPNgsl/7GTEnPX2UV2LCxELNS8te1F4j +9vx1pexj2zVNHacNuHWn+vGm0YZG9bRLcGMO4xzBRHxQjWucGdD7CP9yS6M3NkmZ +wiRRq6crrRHulp52kd3Ok6EL67K/JhRTOeqUSlgvAoGBAK6vKHL3prEs0bDmTEd5 +vPyBofCVXSayK4y8VcwLzwbQIF2QoZjm9UUd3sv29z1zt8UV7KKK/lrTvXMqpIfm +FTNCpOMV+DBpi5oPyolErRhdpu2uDUYo31o6zJa7AGa3ShHF95kqrjJjabqmWsTi +KgkZQwgZxXXY7ATNdSdHfbWP +-----END PRIVATE KEY----- diff --git a/CameraFeed/package.json b/CameraFeed/package.json new file mode 100644 index 0000000000..6878d38296 --- /dev/null +++ b/CameraFeed/package.json @@ -0,0 +1,11 @@ +{ + "name": "camera_feed", + "version": "0.0.1", + "description": "CameraFeed microservice code", + "main": "CameraFeed.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Michael Albers", + "license": "ISC" +} diff --git a/CameraFeed/src/CommandLine.cpp b/CameraFeed/src/CommandLine.cpp new file mode 100644 index 0000000000..10a199c4a6 --- /dev/null +++ b/CameraFeed/src/CommandLine.cpp @@ -0,0 +1,115 @@ +#include +#include +#include + +#include "CommandLine.h" + + +CommandLine::CommandLine(int argc, char **argv) +{ + extern int optind, opterr; + extern char* optarg; + + opterr = 1; + + while (true) + { + enum Option + { + Debug, + Help, + Password, + UserName, + }; + + static struct option options[] = { + {"debug", optional_argument, 0, Debug}, + {"username", required_argument, 0, UserName}, + {"password", required_argument, 0, Password}, + {"help", no_argument, 0, Help}, + {0, 0, 0, 0 } + }; + + int optionIndex = 0; + auto c = ::getopt_long(argc, argv, "", + options, &optionIndex); + if (c == -1) + break; + + switch (c) { + case Debug: + if (optarg) + { + myDebug = std::atoi(optarg); + } + else + { + myDebug = 1; + } + break; + + case Help: + usage(argv[0]); + std::exit(0); + break; + + case Password: + myPassword = optarg; + break; + + case UserName: + myUserName = optarg; + break; + + default: + usage(argv[0]); + std::exit(1); + } + } + + if (argc - optind != 1) + { + throw std::runtime_error("No URI given."); + } + + myURI = argv[optind]; +} + +int CommandLine::getDebug() const noexcept +{ + return myDebug; +} + +std::string CommandLine::getPassword() const noexcept +{ + return myPassword; +} + +std::string CommandLine::getUserName() const noexcept +{ + return myUserName; +} + +std::string CommandLine::getURI() const noexcept +{ + return myURI; +} + +void CommandLine::usage(const char *argv0) +{ + std::cerr << argv0 << " [OPTIONS] camera_host_name" << std::endl + << std::endl + << "Records data from the Amcrest IPM-721S cammer at the " + << "given host name" << std::endl + << std::endl + << "OPTIONS" << std::endl + << " --debug[=level] debug level (1 if no level specified)" + << std::endl + << " --help print this help and exit" + << std::endl + << " --password=password password for camera access" + << std::endl + << " --username=username User name for camera access" + << std::endl; +} + diff --git a/CameraFeed/src/CommandLine.h b/CameraFeed/src/CommandLine.h new file mode 100644 index 0000000000..b40a1d557f --- /dev/null +++ b/CameraFeed/src/CommandLine.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +class CommandLine +{ + public: + CommandLine(int argc, char **argv); + + int getDebug() const noexcept; + std::string getPassword() const noexcept; + std::string getUserName() const noexcept; + std::string getURI() const noexcept; + + void usage(const char *argv0); + + private: + + std::string myUserName; + std::string myPassword; + int myDebug = 0; + std::string myURI; +}; diff --git a/CameraFeed/src/CurlInit.cpp b/CameraFeed/src/CurlInit.cpp new file mode 100644 index 0000000000..d400afe525 --- /dev/null +++ b/CameraFeed/src/CurlInit.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +#include "CurlInit.h" + +CurlInit CurlInit::myCurlInit; + +CurlInit::CurlInit() +{ + auto curlInitStatus = curl_global_init(CURL_GLOBAL_ALL); + if (curlInitStatus) + { + std::cerr << "Critical curl initialization failure: " + << curlInitStatus << std::endl; + std::exit(1); + } +} + +CurlInit::~CurlInit() +{ + curl_global_cleanup(); +} diff --git a/CameraFeed/src/CurlInit.h b/CameraFeed/src/CurlInit.h new file mode 100644 index 0000000000..272cc21a3f --- /dev/null +++ b/CameraFeed/src/CurlInit.h @@ -0,0 +1,22 @@ +#pragma once + +class CurlInit +{ + public: + + CurlInit(const CurlInit&) = delete; + CurlInit(CurlInit&&) = delete; + + ~CurlInit(); + + CurlInit& operator=(const CurlInit&) = delete; + CurlInit& operator=(CurlInit&&) = delete; + + protected: + + CurlInit(); + + private: + + static CurlInit myCurlInit; +}; diff --git a/CameraFeed/src/GetVideo.cpp b/CameraFeed/src/GetVideo.cpp new file mode 100644 index 0000000000..e017ca0a31 --- /dev/null +++ b/CameraFeed/src/GetVideo.cpp @@ -0,0 +1,186 @@ +#include +#include +#include +#include + +#include "GetVideo.h" +#include "JPEGHandler.h" + +// This class reads data from the Amcrest IPM-721S per section 4.1.4 of the +// AMCREST_CGI_SDK_API.pdf (obtainable from Amcrest's website). + +GetVideo::GetVideo(const std::string &theURI, const std::string &theUserName, + const std::string &thePassword, + JPEGHandler &theJPEGCallback, + int theDebug) : + myPassword(thePassword), + myURI(theURI + "/cgi-bin/mjpg/video.cgi?channel=0&subtype=1"), + myUserName(theUserName), + myDebug(theDebug), + myJPEGHandler(theJPEGCallback) +{ + myEasyHandle = curl_easy_init(); + if (NULL == myEasyHandle) + { + throw std::logic_error("Failed to get curl easy handle."); + } + + setEasyOptions(); +} + +GetVideo::~GetVideo() +{ + if (myEasyHandle) + { + curl_easy_cleanup(myEasyHandle); + } +} + +void GetVideo::easyPerform() +{ + auto status = curl_easy_perform(myEasyHandle); + if (CURLE_OK != status) + { + throw std::logic_error{std::string("Error during transfer: ") + + curl_easy_strerror(status)}; + } +} + +void GetVideo::setEasyOptions() +{ + if (myDebug) + { + curl_easy_setopt(myEasyHandle, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(myEasyHandle, CURLOPT_HEADER, 1L); + } + + curl_easy_setopt(myEasyHandle, CURLOPT_URL, myURI.c_str()); + curl_easy_setopt(myEasyHandle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_easy_setopt(myEasyHandle, CURLOPT_USERNAME, myUserName.c_str()); + curl_easy_setopt(myEasyHandle, CURLOPT_PASSWORD, myPassword.c_str()); + + // Certificates on the camera don't verify, so skip it. + // TODO: see what it would take to get cert to verify + // TODO: ssl seems to break the camera (it reboots itself) + // curl_easy_setopt(myEasyHandle, CURLOPT_SSL_VERIFYPEER, 0L); + // curl_easy_setopt(myEasyHandle, CURLOPT_SSL_VERIFYHOST, 0L); + + curl_easy_setopt(myEasyHandle, CURLOPT_HEADERFUNCTION, curlHeaderCallback); + curl_easy_setopt(myEasyHandle, CURLOPT_HEADERDATA, this); + + curl_easy_setopt(myEasyHandle, CURLOPT_WRITEFUNCTION, curlWriteCallback); + curl_easy_setopt(myEasyHandle, CURLOPT_WRITEDATA, this); +} + +void GetVideo::headerCallback(const std::string &theHeader) +{ + if (myBoundary.empty()) + { + std::regex boundaryRE{"Content-Type:.*boundary=([A-Za-z0-9]*)"}; + std::smatch boundaryMatch; + if (std::regex_search(theHeader, boundaryMatch, boundaryRE)) + { + myBoundary = boundaryMatch[1]; + } + } +} + +size_t GetVideo::writeCallback(char *ptr, size_t size, size_t nmemb) +{ + uint32_t ii = 0; + while (ii < nmemb) + { + if (ReadState::Header == myReadState) + { + if (ptr[ii] == '\r' || ptr[ii] == '\n') + { + myLineTerm++; + } + else + { + myHeaderData << ptr[ii]; + } + + if (myLineTerm == 2) + { + myLineTerm = 0; + + if (myDebug) + { + std::cerr << "Header line:" << myHeaderData.str() << std::endl; + } + + if (myHeaderData.str().empty()) + { + // Done with multi-part header. + myReadState = ReadState::JPEG; + } + else + { + static std::regex contentLengthRE{"Content-Length: ([0-9]*)"}; + std::smatch contentLengthMatch; + if (std::regex_search(myHeaderData.str(), contentLengthMatch, + contentLengthRE)) + { + myJPEGSize = std::stoi(contentLengthMatch[1]); + myJPEGData.reset(new char[myJPEGSize]); + myJPEGBytesSaved = 0; + if (myDebug) + { + std::cerr << "jpg size: " << myJPEGSize << std::endl; + } + } + myHeaderData.str(""); + } + } + + ++ii; + } + else if (ReadState::JPEG == myReadState) + { + auto remainingBytes = nmemb - ii; + if (remainingBytes > myJPEGSize) + { + remainingBytes = myJPEGSize; + } + std::memcpy(&myJPEGData.get()[myJPEGBytesSaved], ptr+ii, remainingBytes); + myJPEGBytesSaved += remainingBytes; + myJPEGSize -= remainingBytes; + ii += remainingBytes; + if (myJPEGSize == 0) + { + myJPEGHandler.handleJPEG(myJPEGData.get(), myJPEGBytesSaved); + myReadState = ReadState::Trailer; + } + } + else + { + if (ptr[ii] == '\r' || ptr[ii] == '\n') + { + myLineTerm++; + } + if (myLineTerm == 2) + { + myReadState = ReadState::Header; + } + ++ii; + } + } + + return size * ii; +} + +size_t GetVideo::curlHeaderCallback(char *ptr, size_t size, size_t nmemb, + void *userdata) +{ + std::string header(ptr, size * nmemb); + reinterpret_cast(userdata)->headerCallback(header); + return size * nmemb; +} + +size_t GetVideo::curlWriteCallback(char *ptr, size_t size, size_t nmemb, + void *userdata) +{ + GetVideo *getVideo = reinterpret_cast(userdata); + return getVideo->writeCallback(ptr, size, nmemb); +} diff --git a/CameraFeed/src/GetVideo.h b/CameraFeed/src/GetVideo.h new file mode 100644 index 0000000000..ecc2476399 --- /dev/null +++ b/CameraFeed/src/GetVideo.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include + +class JPEGHandler; + +class GetVideo +{ + public: + + GetVideo() = delete; + + GetVideo(const std::string &theURI, const std::string &theUserName, + const std::string &thePassword, JPEGHandler &theJPEGCallback, + int theDebug); + + GetVideo(const GetVideo&) = delete; + GetVideo(GetVideo&&) = delete; + + ~GetVideo(); + + void easyPerform(); + + protected: + + static size_t curlHeaderCallback(char *ptr, size_t size, size_t nmemb, + void *userdata); + + static size_t curlWriteCallback(char *ptr, size_t size, size_t nmemb, + void *userdata); + + void headerCallback(const std::string &theHeader); + size_t writeCallback(char *ptr, size_t size, size_t nmemb); + + private: + + void setEasyOptions(); + + const std::string myPassword; + const std::string myURI; + const std::string myUserName; + + const int myDebug; + + JPEGHandler &myJPEGHandler; + + enum class ReadState {Header, JPEG, Trailer}; + + ReadState myReadState = ReadState::Header; + std::stringstream myHeaderData; + uint32_t myLineTerm = 0; + + std::unique_ptr myJPEGData; + size_t myJPEGBytesSaved = 0; + uint32_t myJPEGSize = 0; + + std::string myBoundary; + + CURL *myEasyHandle = nullptr; +}; diff --git a/CameraFeed/src/JPEGFileWriter.cpp b/CameraFeed/src/JPEGFileWriter.cpp new file mode 100644 index 0000000000..6d76222b02 --- /dev/null +++ b/CameraFeed/src/JPEGFileWriter.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include + +#include "JPEGFileWriter.h" + +JPEGFileWriter::JPEGFileWriter(const std::string &theDirectory, + const std::string &theFileBase) : + myDirectory(theDirectory), + myFileBase(theFileBase) +{ +} + +void JPEGFileWriter::handleJPEG(const char *theJPEG, size_t theSize) +{ + ++myCount; + std::ostringstream fileName; + fileName << myDirectory << "/" << myFileBase << std::setw(9) + << std::setfill('0') << myCount << ".jpg"; + std::ofstream jpegFile(fileName.str(), std::ios::binary); + auto thisErrno = errno; + if (! jpegFile.is_open()) + { + throw std::logic_error("Failed to open " + fileName.str() + ": " + + std::strerror(thisErrno)); + } + + jpegFile.write(theJPEG, theSize); + jpegFile.close(); +} diff --git a/CameraFeed/src/JPEGFileWriter.h b/CameraFeed/src/JPEGFileWriter.h new file mode 100644 index 0000000000..6667e1b16b --- /dev/null +++ b/CameraFeed/src/JPEGFileWriter.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "JPEGHandler.h" + +class JPEGFileWriter : public JPEGHandler +{ + public: + JPEGFileWriter() = delete; + JPEGFileWriter(const std::string &theDirectory, + const std::string &theFileBase); + + virtual void handleJPEG(const char *theJPEG, size_t theSize) override; + + + private: + const std::string myDirectory; + const std::string myFileBase; + uint32_t myCount = 0; +}; diff --git a/CameraFeed/src/JPEGHandler.h b/CameraFeed/src/JPEGHandler.h new file mode 100644 index 0000000000..0f8f007490 --- /dev/null +++ b/CameraFeed/src/JPEGHandler.h @@ -0,0 +1,9 @@ +#pragma once + +class JPEGHandler +{ + public: + virtual ~JPEGHandler() = default; + + virtual void handleJPEG(const char *theJPEG, size_t theSize) = 0; +}; diff --git a/CameraFeed/src/Makefile b/CameraFeed/src/Makefile new file mode 100644 index 0000000000..91fb730434 --- /dev/null +++ b/CameraFeed/src/Makefile @@ -0,0 +1,54 @@ + +SRCS := CommandLine.cpp \ + CurlInit.cpp \ + GetVideo.cpp \ + JPEGFileWriter.cpp \ + main.cpp + +EXE := AmcrestIPM-721S_StreamReader + +MAKEFLAGS := --no-print-directory +DEPEND_FILE := .dependlist + +# TODO: need to install libcurl4-gnutls-dev package +CURL_C_FLAGS := $(shell curl-config --cflags) +CURL_LD_FLAGS := $(shell curl-config --libs) + +CC := g++ +CFLAGS := --std=c++11 -g -Wall $(CURL_C_FLAGS) $(INC_DIRS) + +LD := g++ +LDFLAGS := +LIBS := $(CURL_LD_FLAGS) + +OBJS := $(SRCS:%.cpp=%.o) + +all: $(EXE) + +$(EXE): $(OBJS) + @echo "Linking $(EXE)" + @$(LD) $(LDFLAGS) -o $(EXE) $(OBJS) $(LIBS) + +%.o:%.cpp + @echo "Compiling $<" + @$(CC) $(CFLAGS) -o $@ -c $< + +.PHONY: clean +clean: + @echo "Cleaning $(EXE)" + @$(RM) $(OBJS) $(EXE) $(DEPEND_FILE) *~ + +.PHONY: depend +depend: + @echo "Building dependencies for: $(SRCS)" + @/bin/cat < /dev/null > $(DEPEND_FILE); \ + for file in $(SRCS) ; do \ + srcDepend=$${file}.d; \ + objFile=`echo $$file | /bin/sed -e 's~\.cpp~.o~'`; \ + $(CC) $(CFLAGS) -MM -MT $$objFile \ + -MF $$srcDepend $$file; \ + /bin/cat < $$srcDepend >> $(DEPEND_FILE); \ + $(RM) $$srcDepend; \ + done + +-include $(DEPEND_FILE) diff --git a/CameraFeed/src/main.cpp b/CameraFeed/src/main.cpp new file mode 100644 index 0000000000..8d9d0b45e0 --- /dev/null +++ b/CameraFeed/src/main.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +#include "CommandLine.h" +#include "GetVideo.h" +#include "JPEGFileWriter.h" + +//http://stackoverflow.com/questions/12797647/ffmpeg-how-to-mux-mjpeg-encoded-data-into-mp4-or-avi-container-c +//http://video.stackexchange.com/questions/7903/how-to-losslessly-encode-a-jpg-image-sequence-to-a-video-in-ffmpeg +int main(int argc, char **argv) +{ + try + { + CommandLine commandLine(argc, argv); + JPEGFileWriter jpegWriter("/tmp/jpegs", ""); + GetVideo getVideo(commandLine.getURI(), commandLine.getUserName(), + commandLine.getPassword(), jpegWriter, + commandLine.getDebug()); + getVideo.easyPerform(); + } + catch (const std::exception &exception) + { + std::cerr << argv[0] << ": " << exception.what() << std::endl; + } + + return 0; +} From cfcbb9636ba1659e16808e142ab36a604c5b0798 Mon Sep 17 00:00:00 2001 From: Ramiro Arenivar Date: Wed, 23 Mar 2016 01:30:32 -0600 Subject: [PATCH 06/81] added UI for application, along with login/registration functionality --- .gitignore | 1 + camera5799/.dockerignore | 1 + camera5799/.gitignore | 2 + camera5799/.meteor/.finished-upgraders | 12 ++ camera5799/.meteor/.gitignore | 1 + camera5799/.meteor/.id | 7 + camera5799/.meteor/cordova-plugins | 0 camera5799/.meteor/packages | 26 +++ camera5799/.meteor/platforms | 2 + camera5799/.meteor/release | 1 + camera5799/.meteor/versions | 88 ++++++++++ camera5799/Dockerfile | 14 ++ camera5799/README | 1 + camera5799/client/helpers/config.js | 3 + camera5799/client/helpers/errors.js | 6 + camera5799/client/helpers/handlebars.js | 8 + camera5799/client/main.html | 3 + camera5799/client/main.js | 0 camera5799/client/stylesheets/style.css | 160 ++++++++++++++++++ .../client/templates/application/layout.html | 9 + .../client/templates/application/layout.js | 24 +++ .../templates/application/not_found.html | 6 + .../templates/includes/access_denied.html | 6 + .../client/templates/includes/errors.html | 14 ++ .../client/templates/includes/errors.js | 12 ++ .../client/templates/includes/header.html | 31 ++++ .../client/templates/includes/header.js | 12 ++ .../client/templates/includes/loading.html | 3 + .../client/templates/views/add_camera.html | 24 +++ .../templates/views/browse_cameras.html | 11 ++ .../client/templates/views/browse_videos.html | 13 ++ .../client/templates/views/camera_detail.html | 27 +++ .../client/templates/views/home_page.html | 12 ++ camera5799/lib/permissions.js | 4 + camera5799/lib/router.js | 25 +++ 35 files changed, 569 insertions(+) create mode 100644 .gitignore create mode 100644 camera5799/.dockerignore create mode 100644 camera5799/.gitignore create mode 100755 camera5799/.meteor/.finished-upgraders create mode 100755 camera5799/.meteor/.gitignore create mode 100755 camera5799/.meteor/.id create mode 100755 camera5799/.meteor/cordova-plugins create mode 100755 camera5799/.meteor/packages create mode 100755 camera5799/.meteor/platforms create mode 100644 camera5799/.meteor/release create mode 100644 camera5799/.meteor/versions create mode 100644 camera5799/Dockerfile create mode 100644 camera5799/README create mode 100755 camera5799/client/helpers/config.js create mode 100755 camera5799/client/helpers/errors.js create mode 100755 camera5799/client/helpers/handlebars.js create mode 100755 camera5799/client/main.html create mode 100755 camera5799/client/main.js create mode 100755 camera5799/client/stylesheets/style.css create mode 100755 camera5799/client/templates/application/layout.html create mode 100755 camera5799/client/templates/application/layout.js create mode 100755 camera5799/client/templates/application/not_found.html create mode 100755 camera5799/client/templates/includes/access_denied.html create mode 100755 camera5799/client/templates/includes/errors.html create mode 100755 camera5799/client/templates/includes/errors.js create mode 100755 camera5799/client/templates/includes/header.html create mode 100755 camera5799/client/templates/includes/header.js create mode 100755 camera5799/client/templates/includes/loading.html create mode 100644 camera5799/client/templates/views/add_camera.html create mode 100644 camera5799/client/templates/views/browse_cameras.html create mode 100644 camera5799/client/templates/views/browse_videos.html create mode 100644 camera5799/client/templates/views/camera_detail.html create mode 100644 camera5799/client/templates/views/home_page.html create mode 100755 camera5799/lib/permissions.js create mode 100755 camera5799/lib/router.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..e43b0f9889 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/camera5799/.dockerignore b/camera5799/.dockerignore new file mode 100644 index 0000000000..8a628b6d34 --- /dev/null +++ b/camera5799/.dockerignore @@ -0,0 +1 @@ +.meteor/local diff --git a/camera5799/.gitignore b/camera5799/.gitignore new file mode 100644 index 0000000000..68729d6bfc --- /dev/null +++ b/camera5799/.gitignore @@ -0,0 +1,2 @@ +.idea/ +.DS_Store \ No newline at end of file diff --git a/camera5799/.meteor/.finished-upgraders b/camera5799/.meteor/.finished-upgraders new file mode 100755 index 0000000000..61ee313230 --- /dev/null +++ b/camera5799/.meteor/.finished-upgraders @@ -0,0 +1,12 @@ +# This file contains information which helps Meteor properly upgrade your +# app when you run 'meteor update'. You should check it into version control +# with your project. + +notices-for-0.9.0 +notices-for-0.9.1 +0.9.4-platform-file +notices-for-facebook-graph-api-2 +1.2.0-standard-minifiers-package +1.2.0-meteor-platform-split +1.2.0-cordova-changes +1.2.0-breaking-changes diff --git a/camera5799/.meteor/.gitignore b/camera5799/.meteor/.gitignore new file mode 100755 index 0000000000..4083037423 --- /dev/null +++ b/camera5799/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/camera5799/.meteor/.id b/camera5799/.meteor/.id new file mode 100755 index 0000000000..7ade38d955 --- /dev/null +++ b/camera5799/.meteor/.id @@ -0,0 +1,7 @@ +# This file contains a token that is unique to your project. +# Check it into your repository along with the rest of this directory. +# It can be used for purposes such as: +# - ensuring you don't accidentally deploy one app on top of another +# - providing package authors with aggregated statistics + +adtvcqcdlr761u2znzz diff --git a/camera5799/.meteor/cordova-plugins b/camera5799/.meteor/cordova-plugins new file mode 100755 index 0000000000..e69de29bb2 diff --git a/camera5799/.meteor/packages b/camera5799/.meteor/packages new file mode 100755 index 0000000000..f11a5ed229 --- /dev/null +++ b/camera5799/.meteor/packages @@ -0,0 +1,26 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +meteor-base # Packages every Meteor app needs to have +mobile-experience # Packages for a great mobile UX +mongo # The database Meteor supports right now +blaze-html-templates # Compile .html files into Meteor Blaze views +session # Client-side reactive dictionary for your app +jquery # Helpful client-side library +tracker # Meteor's client-side reactive programming library + +standard-minifiers # JS/CSS minifiers run for production mode +es5-shim # ECMAScript 5 compatibility for older browsers. +ecmascript # Enable ECMAScript2015+ syntax in app code + +twbs:bootstrap +underscore +iron:router@1.0.0-rc.1 +sacha:spin +accounts-password +ian:accounts-ui-bootstrap-3 +check +audit-argument-checks diff --git a/camera5799/.meteor/platforms b/camera5799/.meteor/platforms new file mode 100755 index 0000000000..efeba1b50c --- /dev/null +++ b/camera5799/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/camera5799/.meteor/release b/camera5799/.meteor/release new file mode 100644 index 0000000000..3a05e0a2f7 --- /dev/null +++ b/camera5799/.meteor/release @@ -0,0 +1 @@ +METEOR@1.2.1 diff --git a/camera5799/.meteor/versions b/camera5799/.meteor/versions new file mode 100644 index 0000000000..98cdcbad6b --- /dev/null +++ b/camera5799/.meteor/versions @@ -0,0 +1,88 @@ +accounts-base@1.2.2 +accounts-password@1.1.4 +anti:i18n@0.4.3 +audit-argument-checks@1.0.4 +autoupdate@1.2.4 +babel-compiler@5.8.24_1 +babel-runtime@0.1.4 +base64@1.0.4 +binary-heap@1.0.4 +blaze@2.1.3 +blaze-html-templates@1.0.1 +blaze-tools@1.0.4 +boilerplate-generator@1.0.4 +caching-compiler@1.0.0 +caching-html-compiler@1.0.2 +callback-hook@1.0.4 +check@1.1.0 +ddp@1.2.2 +ddp-client@1.2.1 +ddp-common@1.2.2 +ddp-rate-limiter@1.0.0 +ddp-server@1.2.2 +deps@1.0.9 +diff-sequence@1.0.1 +ecmascript@0.1.6 +ecmascript-runtime@0.2.6 +ejson@1.0.7 +email@1.0.8 +es5-shim@4.1.14 +fastclick@1.0.7 +geojson-utils@1.0.4 +hot-code-push@1.0.0 +html-tools@1.0.5 +htmljs@1.0.5 +http@1.1.1 +ian:accounts-ui-bootstrap-3@1.2.89 +id-map@1.0.4 +iron:controller@1.0.12 +iron:core@1.0.11 +iron:dynamic-template@1.0.12 +iron:layout@1.0.12 +iron:location@1.0.11 +iron:middleware-stack@1.0.11 +iron:router@1.0.12 +iron:url@1.0.11 +jquery@1.11.4 +launch-screen@1.0.4 +livedata@1.0.15 +localstorage@1.0.5 +logging@1.0.8 +meteor@1.1.10 +meteor-base@1.0.1 +minifiers@1.1.7 +minimongo@1.0.10 +mobile-experience@1.0.1 +mobile-status-bar@1.0.6 +mongo@1.1.3 +mongo-id@1.0.1 +npm-bcrypt@0.7.8_2 +npm-mongo@1.4.39_1 +observe-sequence@1.0.7 +ordered-dict@1.0.4 +promise@0.5.1 +random@1.0.5 +rate-limit@1.0.0 +reactive-dict@1.1.3 +reactive-var@1.0.6 +reload@1.1.4 +retry@1.0.4 +routepolicy@1.0.6 +sacha:spin@2.3.1 +service-configuration@1.0.5 +session@1.1.1 +sha@1.0.4 +spacebars@1.0.7 +spacebars-compiler@1.0.7 +srp@1.0.4 +standard-minifiers@1.0.2 +stylus@2.511.1 +templating@1.1.5 +templating-tools@1.0.0 +tracker@1.0.9 +twbs:bootstrap@3.3.6 +ui@1.0.8 +underscore@1.0.4 +url@1.0.5 +webapp@1.2.3 +webapp-hashing@1.0.5 diff --git a/camera5799/Dockerfile b/camera5799/Dockerfile new file mode 100644 index 0000000000..782731fe73 --- /dev/null +++ b/camera5799/Dockerfile @@ -0,0 +1,14 @@ +FROM ubuntu:latest +MAINTAINER ramiro@rarenivar.com + +RUN locale-gen en_US.UTF-8 +RUN mkdir -p src +RUN cd src +RUN apt-get install -y curl +RUN curl https://install.meteor.com/ | sh +RUN meteor update +RUN apt-get clean +COPY . /src +WORKDIR src/ + +CMD ["meteor"] diff --git a/camera5799/README b/camera5799/README new file mode 100644 index 0000000000..12f101c0e6 --- /dev/null +++ b/camera5799/README @@ -0,0 +1 @@ +Contains the web app for the Cloud Computing project \ No newline at end of file diff --git a/camera5799/client/helpers/config.js b/camera5799/client/helpers/config.js new file mode 100755 index 0000000000..fa2f0d0117 --- /dev/null +++ b/camera5799/client/helpers/config.js @@ -0,0 +1,3 @@ +Accounts.ui.config({ + passwordSignupFields: 'USERNAME_ONLY' +}); \ No newline at end of file diff --git a/camera5799/client/helpers/errors.js b/camera5799/client/helpers/errors.js new file mode 100755 index 0000000000..1991c316d6 --- /dev/null +++ b/camera5799/client/helpers/errors.js @@ -0,0 +1,6 @@ +// Local (client-only) collection +Errors = new Mongo.Collection(null); + +throwError = function(message) { + Errors.insert({message: message}) +} \ No newline at end of file diff --git a/camera5799/client/helpers/handlebars.js b/camera5799/client/helpers/handlebars.js new file mode 100755 index 0000000000..ef5db69d66 --- /dev/null +++ b/camera5799/client/helpers/handlebars.js @@ -0,0 +1,8 @@ +Template.registerHelper('pluralize', function(n, thing) { + // fairly stupid pluralizer + if (n === 1) { + return '1 ' + thing; + } else { + return n + ' ' + thing + 's'; + } +}); \ No newline at end of file diff --git a/camera5799/client/main.html b/camera5799/client/main.html new file mode 100755 index 0000000000..96ce4b01c4 --- /dev/null +++ b/camera5799/client/main.html @@ -0,0 +1,3 @@ + + Camera 5799 + diff --git a/camera5799/client/main.js b/camera5799/client/main.js new file mode 100755 index 0000000000..e69de29bb2 diff --git a/camera5799/client/stylesheets/style.css b/camera5799/client/stylesheets/style.css new file mode 100755 index 0000000000..67206b7582 --- /dev/null +++ b/camera5799/client/stylesheets/style.css @@ -0,0 +1,160 @@ +.grid-block, .main, .post, .comments li, .comment-form { + background: #fff; + border-radius: 3px; + padding: 10px; + margin-bottom: 10px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); } + +body { + background: #eee; + color: #666666; } + +#main { + position: relative; +} +.page { + position: absolute; + top: 0px; + width: 100%; +} + +.navbar { + margin-bottom: 10px; } + /* line 32, ../sass/style.scss */ + .navbar .navbar-inner { + border-radius: 0px 0px 3px 3px; } + +#spinner { + height: 300px; } + +.post { + /* For modern browsers */ + /* For IE 6/7 (trigger hasLayout) */ + *zoom: 1; + position: relative; + opacity: 1; } + .post:before, .post:after { + content: ""; + display: table; } + .post:after { + clear: both; } + .post.invisible { + opacity: 0; } + .post.instant { + -webkit-transition: none; + -moz-transition: none; + -o-transition: none; + transition: none; } + .post.animate{ + -webkit-transition: all 300ms 0ms; + -moz-transition: all 300ms 0ms ease-in; + -o-transition: all 300ms 0ms ease-in; + transition: all 300ms 0ms ease-in; } + .post .upvote { + display: block; + margin: 7px 12px 0 0; + float: left; } + .post .post-content { + float: left; } + .post .post-content h3 { + margin: 0; + line-height: 1.4; + font-size: 18px; } + .post .post-content h3 a { + display: inline-block; + margin-right: 5px; } + .post .post-content h3 span { + font-weight: normal; + font-size: 14px; + display: inline-block; + color: #aaaaaa; } + .post .post-content p { + margin: 0; } + .post .discuss { + display: block; + float: right; + margin-top: 7px; } + +.comments { + list-style-type: none; + margin: 0; } + .comments li h4 { + font-size: 16px; + margin: 0; } + .comments li h4 .date { + font-size: 12px; + font-weight: normal; } + .comments li h4 a { + font-size: 12px; } + .comments li p:last-child { + margin-bottom: 0; } + +.dropdown-menu span { + display: block; + padding: 3px 20px; + clear: both; + line-height: 20px; + color: #bbb; + white-space: nowrap; } + +.load-more { + display: block; + border-radius: 3px; + background: rgba(0, 0, 0, 0.05); + text-align: center; + height: 60px; + line-height: 60px; + margin-bottom: 10px; } + .load-more:hover { + text-decoration: none; + background: rgba(0, 0, 0, 0.1); } + +.posts .spinner-container{ + position: relative; + height: 100px; +} + +.jumbotron{ + text-align: center; +} +.jumbotron h2{ + font-size: 60px; + font-weight: 100; +} + +@-webkit-keyframes fadeOut { + 0% {opacity: 0;} + 10% {opacity: 1;} + 90% {opacity: 1;} + 100% {opacity: 0;} +} + +@keyframes fadeOut { + 0% {opacity: 0;} + 10% {opacity: 1;} + 90% {opacity: 1;} + 100% {opacity: 0;} +} + +.errors{ + position: fixed; + z-index: 10000; + padding: 10px; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + pointer-events: none; +} +.alert { + animation: fadeOut 2700ms ease-in 0s 1 forwards; + -webkit-animation: fadeOut 2700ms ease-in 0s 1 forwards; + -moz-animation: fadeOut 2700ms ease-in 0s 1 forwards; + width: 250px; + float: right; + clear: both; + margin-bottom: 5px; + pointer-events: auto; +} diff --git a/camera5799/client/templates/application/layout.html b/camera5799/client/templates/application/layout.html new file mode 100755 index 0000000000..43559dece3 --- /dev/null +++ b/camera5799/client/templates/application/layout.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/camera5799/client/templates/application/layout.js b/camera5799/client/templates/application/layout.js new file mode 100755 index 0000000000..d0aebc6ca1 --- /dev/null +++ b/camera5799/client/templates/application/layout.js @@ -0,0 +1,24 @@ +Template.layout.onRendered(function() { + this.find('#main')._uihooks = { + insertElement: function(node, next) { + console.log("insert element"); + $(node) + .hide() + .insertBefore(next) + .fadeIn(); + //setTimeout(function(){ + // $(node) + // .hide() + // .insertBefore(next) + // .fadeIn(); + //}, 500); + + }, + removeElement: function(node) { + console.log("remove element"); + $(node).fadeOut(function() { + $(this).remove(); + }); + } + } +}); \ No newline at end of file diff --git a/camera5799/client/templates/application/not_found.html b/camera5799/client/templates/application/not_found.html new file mode 100755 index 0000000000..646a605194 --- /dev/null +++ b/camera5799/client/templates/application/not_found.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/camera5799/client/templates/includes/access_denied.html b/camera5799/client/templates/includes/access_denied.html new file mode 100755 index 0000000000..020cb00265 --- /dev/null +++ b/camera5799/client/templates/includes/access_denied.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/camera5799/client/templates/includes/errors.html b/camera5799/client/templates/includes/errors.html new file mode 100755 index 0000000000..598c140f5c --- /dev/null +++ b/camera5799/client/templates/includes/errors.html @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/camera5799/client/templates/includes/errors.js b/camera5799/client/templates/includes/errors.js new file mode 100755 index 0000000000..94e41d8467 --- /dev/null +++ b/camera5799/client/templates/includes/errors.js @@ -0,0 +1,12 @@ +Template.errors.helpers({ + errors: function() { + return Errors.find(); + } +}); + +Template.error.onRendered(function() { + var error = this.data; + Meteor.setTimeout(function () { + Errors.remove(error._id); + }, 3000); +}); \ No newline at end of file diff --git a/camera5799/client/templates/includes/header.html b/camera5799/client/templates/includes/header.html new file mode 100755 index 0000000000..f5db1d8090 --- /dev/null +++ b/camera5799/client/templates/includes/header.html @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/camera5799/client/templates/includes/header.js b/camera5799/client/templates/includes/header.js new file mode 100755 index 0000000000..3bd9c83ef9 --- /dev/null +++ b/camera5799/client/templates/includes/header.js @@ -0,0 +1,12 @@ +Template.header.helpers({ + activeRouteClass: function(/* route names */) { + var args = Array.prototype.slice.call(arguments, 0); + args.pop(); + + var active = _.any(args, function(name) { + return Router.current() && Router.current().route.getName() === name + }); + + return active && 'active'; + } +}); \ No newline at end of file diff --git a/camera5799/client/templates/includes/loading.html b/camera5799/client/templates/includes/loading.html new file mode 100755 index 0000000000..494b251050 --- /dev/null +++ b/camera5799/client/templates/includes/loading.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/camera5799/client/templates/views/add_camera.html b/camera5799/client/templates/views/add_camera.html new file mode 100644 index 0000000000..44038d862f --- /dev/null +++ b/camera5799/client/templates/views/add_camera.html @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/camera5799/client/templates/views/browse_cameras.html b/camera5799/client/templates/views/browse_cameras.html new file mode 100644 index 0000000000..86ba9fe8e4 --- /dev/null +++ b/camera5799/client/templates/views/browse_cameras.html @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/camera5799/client/templates/views/browse_videos.html b/camera5799/client/templates/views/browse_videos.html new file mode 100644 index 0000000000..4f40077b1f --- /dev/null +++ b/camera5799/client/templates/views/browse_videos.html @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/camera5799/client/templates/views/camera_detail.html b/camera5799/client/templates/views/camera_detail.html new file mode 100644 index 0000000000..2f25162d08 --- /dev/null +++ b/camera5799/client/templates/views/camera_detail.html @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/camera5799/client/templates/views/home_page.html b/camera5799/client/templates/views/home_page.html new file mode 100644 index 0000000000..7153233d53 --- /dev/null +++ b/camera5799/client/templates/views/home_page.html @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/camera5799/lib/permissions.js b/camera5799/lib/permissions.js new file mode 100755 index 0000000000..5858668a7c --- /dev/null +++ b/camera5799/lib/permissions.js @@ -0,0 +1,4 @@ +//// check that the userId specified owns the documents +//ownsDocument = function(userId, doc) { +// return doc && doc.userId === userId; +//} \ No newline at end of file diff --git a/camera5799/lib/router.js b/camera5799/lib/router.js new file mode 100755 index 0000000000..6ae4e3d66a --- /dev/null +++ b/camera5799/lib/router.js @@ -0,0 +1,25 @@ +Router.configure({ + layoutTemplate: 'layout', + loadingTemplate: 'loading', + notFoundTemplate: 'notFound' +}); + +Router.route('/', { + name: 'homePage' +}); + +Router.route('/addCamera', { + name: 'addCamera' +}); + +Router.route('/browseVideos', { + name: 'browseVideos' +}); + +Router.route('/browseCameras', { + name: 'browseCameras' +}); + +Router.route('/cameraDetail', { + name: 'cameraDetail' +}); \ No newline at end of file From 3af25a667228650097af4bfa76f11542f004cbbb Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Thu, 24 Mar 2016 17:37:13 -0600 Subject: [PATCH 07/81] add JWT support --- server/webfront/README.md | 57 ++++++++++++++++++++ server/webfront/webfront.go | 102 ++++++++++++++++++++++++++++++++++-- 2 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 server/webfront/README.md diff --git a/server/webfront/README.md b/server/webfront/README.md new file mode 100644 index 0000000000..de4cb39e1c --- /dev/null +++ b/server/webfront/README.md @@ -0,0 +1,57 @@ +* To get a token: + + ``curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}'`` + + in my case that returned: + + ``{"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}`` + + Example: + ``` +[jvd@laika webfront (master *=)]$ curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}' +* Trying ::1... +* Connected to localhost (::1) port 9000 (#0) +* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 +* Server certificate: CU +> POST /login HTTP/1.1 +> Host: localhost:9000 +> User-Agent: curl/7.43.0 +> Accept: */* +> Content-Type:application/json +> Content-Length: 39 +> +* upload completely sent off: 39 out of 39 bytes +< HTTP/1.1 200 OK +< Content-Type: application/json +< Date: Thu, 24 Mar 2016 23:30:19 GMT +< Content-Length: 157 +< +* Connection #0 to host localhost left intact +{"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}[jvd@laika webfront (master *=)]$ + ``` + * To use a token: + ``curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r`` + + Example: + + ``` + [jvd@laika webfront (master *%=)]$ curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r + * Trying ::1... + * Connected to localhost (::1) port 9000 (#0) + * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + * Server certificate: CU + > GET /8003/r HTTP/1.1 + > Host: localhost:9000 + > User-Agent: curl/7.43.0 + > Accept: */* + > Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk + > + < HTTP/1.1 200 OK + < Content-Length: 24 + < Content-Type: text/plain; charset=utf-8 + < Date: Thu, 24 Mar 2016 23:34:08 GMT + < + Hitting 8003 with /boo1 + * Connection #0 to host localhost left intact + [jvd@laika webfront (master *%=)]$ +``` \ No newline at end of file diff --git a/server/webfront/webfront.go b/server/webfront/webfront.go index 769a3e8a89..fb77667a05 100644 --- a/server/webfront/webfront.go +++ b/server/webfront/webfront.go @@ -4,20 +4,32 @@ package main import ( + // "crypto/sha1" "crypto/tls" + // "encoding/hex" "encoding/json" "flag" + "fmt" + jwt "github.com/dgrijalva/jwt-go" + "io/ioutil" "log" - // "net" "net/http" "net/http/httputil" "os" - // "strconv" "strings" "sync" "time" ) +type TokenResponse struct { + Token string +} + +type loginJson struct { + User string `json:"user"` + Password string `json:"password"` +} + var ( httpsAddr = flag.String("https", "", "HTTPS listen address (leave empty to disable)") certFile = flag.String("https_cert", "", "HTTPS certificate file") @@ -34,12 +46,12 @@ func main() { } // override the default so we can use self-signed certs on our microservices + // and use a self-signed cert in this server http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - http.ListenAndServeTLS(*httpsAddr, *certFile, *keyFile, s) } -// Server implements an http.Handler that acts asither a reverse proxy +// Server implements an http.Handler that acts as a reverse proxy type Server struct { mu sync.RWMutex // guards the fields below last time.Time @@ -56,6 +68,34 @@ type Rule struct { handler http.Handler } +// func UnauthorizedResponse(w http.ResponseWriter) { +// resp := &http.Response{} +// resp.StatusCode = http.StatusUnauthorized +// http.Handler.ServeHTTP(w) +// } + +// to get a token: +// curl --header "Content-Type:application/json" -XPOST http://host:port/login -d'{"u":"yourusername", "p":"yourpassword}' + +func validateToken(tokenString string) (*jwt.Token, error) { + + tokenString = strings.Replace(tokenString, "Bearer ", "", 1) + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Don't forget to validate the alg is what you expect: + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return []byte("CAmeRAFiveSevenNineNine"), nil + }) + + if err == nil && token.Valid { + log.Println("TOKEN IS GOOD -- user:", token.Claims["userid"], " role:", token.Claims["role"]) + } else { + log.Println("TOKEN IS BAD", err) + } + return token, err +} + // NewServer constructs a Server that reads rules from file with a period // specified by poll. func NewServer(file string, poll time.Duration) (*Server, error) { @@ -70,6 +110,53 @@ func NewServer(file string, poll time.Duration) (*Server, error) { // ServeHTTP matches the Request with a Rule and, if found, serves the // request with the Rule's handler. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/login" { + log.Println("/login.... Do some smarts") + username := "" + password := "" + body, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Println("Error reading body: ", err.Error()) + http.Error(w, "Error reading body: "+err.Error(), http.StatusBadRequest) + return + } + var lj loginJson + log.Println(body) + err = json.Unmarshal(body, &lj) + if err != nil { + log.Println("Error unmarshalling JSON: ", err.Error()) + http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) + return + } + username = lj.User + password = lj.Password + token := jwt.New(jwt.SigningMethodHS256) + token.Claims["User"] = username + token.Claims["Password"] = password + token.Claims["exp"] = time.Now().Add(time.Hour * 24).Unix() + tokenString, err := token.SignedString([]byte("CAmeRAFiveSevenNineNine")) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + js, err := json.Marshal(TokenResponse{Token: tokenString}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(js) + return + } + // TODO JvD ^^ move into own function + token, err := validateToken(r.Header.Get("Authorization")) + if err != nil { + log.Println("No valid token found!") + w.WriteHeader(http.StatusForbidden) + return + } + log.Println("Token:", token.Claims["userid"]) + if h := s.handler(r); h != nil { h.ServeHTTP(w, r) return @@ -77,6 +164,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, "Not found.", http.StatusNotFound) } +func rejectNoToken(handler http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + } +} + // handler returns the appropriate Handler for the given Request, // or nil if none found. func (s *Server) handler(req *http.Request) http.Handler { @@ -91,7 +184,6 @@ func (s *Server) handler(req *http.Request) http.Handler { for _, r := range s.rules { // log.Println(p, "==", r.Path) if strings.HasPrefix(p, r.Path) { - // Todo JvD Auth check goes here?? return r.handler } } From 771e440a9c925c5ebc20436653afe3533c52112d Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Thu, 24 Mar 2016 17:39:22 -0600 Subject: [PATCH 08/81] formatting --- server/webfront/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/server/webfront/README.md b/server/webfront/README.md index de4cb39e1c..6d7ff26e62 100644 --- a/server/webfront/README.md +++ b/server/webfront/README.md @@ -30,6 +30,7 @@ {"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}[jvd@laika webfront (master *=)]$ ``` * To use a token: + ``curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r`` Example: From f1795b875aa6ccf7f07bb33a0084ccf29bd14b38 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Thu, 24 Mar 2016 17:44:16 -0600 Subject: [PATCH 09/81] add fail example --- server/webfront/README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/server/webfront/README.md b/server/webfront/README.md index 6d7ff26e62..2ff8e4302a 100644 --- a/server/webfront/README.md +++ b/server/webfront/README.md @@ -30,7 +30,7 @@ {"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}[jvd@laika webfront (master *=)]$ ``` * To use a token: - + ``curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r`` Example: @@ -55,4 +55,22 @@ Hitting 8003 with /boo1 * Connection #0 to host localhost left intact [jvd@laika webfront (master *%=)]$ + + [jvd@laika webfront (master=)]$ curl --insecure -H'Authorization: Bearer FAKETOKEN' -Lkvs https://localhost:9000/8003/r * Trying ::1... + * Connected to localhost (::1) port 9000 (#0) + * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + * Server certificate: CU + > GET /8003/r HTTP/1.1 + > Host: localhost:9000 + > User-Agent: curl/7.43.0 + > Accept: */* + > Authorization: Bearer FAKETOKEN + > + < HTTP/1.1 403 Forbidden + < Date: Thu, 24 Mar 2016 23:43:11 GMT + < Content-Length: 0 + < Content-Type: text/plain; charset=utf-8 + < + * Connection #0 to host localhost left intact + [jvd@laika webfront (master=)]$ ``` \ No newline at end of file From cae31c865cef6221ee4acb265a0bc96c4c52166e Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Thu, 24 Mar 2016 17:57:12 -0600 Subject: [PATCH 10/81] some cleanup --- server/webfront/README.md | 22 ++++++++++++++++++++++ server/webfront/webfront.go | 17 +++++------------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/server/webfront/README.md b/server/webfront/README.md index 2ff8e4302a..0b1f7d63ff 100644 --- a/server/webfront/README.md +++ b/server/webfront/README.md @@ -1,3 +1,25 @@ +This application - webfront is a reverse proxy written in go that can front any number of microservices. It uses a rules file to map from requested host/path to microservice host/port/path. Example rule file: + + ``` + [ + {"Host": "local.com", "Path" : "/8001", "Forward": "localhost:8001"}, + {"Host": "local.com", "Path" : "/8002", "Forward": "localhost:8002"}, + {"Host": "local.com", "Path" : "/8003", "Forward": "localhost:8003"}, + {"Host": "local.com", "Path" : "/8004", "Forward": "localhost:8004"}, + {"Host": "local.com", "Path" : "/8005", "Forward": "localhost:8005"}, + {"Host": "local.com", "Path" : "/8006", "Forward": "localhost:8006"}, + {"Host": "local.com", "Path" : "/8007", "Forward": "localhost:8007"} + ] + ``` + +No restart is needed to re-read the rule file and apply; within 60 seconds of a change in the file, it will pick up the new mappings. + +* To run + + ``go run webfront.go -rules=rules.json -https=:9000 -https_cert=server.pem -https_key=server.key``` + + (or compile a binary, and run that) + * To get a token: ``curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}'`` diff --git a/server/webfront/webfront.go b/server/webfront/webfront.go index fb77667a05..863c650b4c 100644 --- a/server/webfront/webfront.go +++ b/server/webfront/webfront.go @@ -63,20 +63,10 @@ type Rule struct { Host string // to match against request Host header Path string // to match against a path (start) Forward string // reverse proxy map-to - // Serve string // non-empty if file server handler http.Handler } -// func UnauthorizedResponse(w http.ResponseWriter) { -// resp := &http.Response{} -// resp.StatusCode = http.StatusUnauthorized -// http.Handler.ServeHTTP(w) -// } - -// to get a token: -// curl --header "Content-Type:application/json" -XPOST http://host:port/login -d'{"u":"yourusername", "p":"yourpassword}' - func validateToken(tokenString string) (*jwt.Token, error) { tokenString = strings.Replace(tokenString, "Bearer ", "", 1) @@ -111,7 +101,6 @@ func NewServer(file string, poll time.Duration) (*Server, error) { // request with the Rule's handler. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/login" { - log.Println("/login.... Do some smarts") username := "" password := "" body, err := ioutil.ReadAll(r.Body) @@ -130,6 +119,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } username = lj.User password = lj.Password + + // TODO JvD - check username / password against Database here! + token := jwt.New(jwt.SigningMethodHS256) token.Claims["User"] = username token.Claims["Password"] = password @@ -148,13 +140,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write(js) return } - // TODO JvD ^^ move into own function token, err := validateToken(r.Header.Get("Authorization")) if err != nil { log.Println("No valid token found!") w.WriteHeader(http.StatusForbidden) return } + // TODO JvD ^^ move into own function + log.Println("Token:", token.Claims["userid"]) if h := s.handler(r); h != nil { From 94cc40cafd3825ad45d4153d317d6510c698f041 Mon Sep 17 00:00:00 2001 From: Michael Albers Date: Thu, 24 Mar 2016 20:52:36 -0600 Subject: [PATCH 11/81] Add MongoDB back end to CameraFeed. --- CameraFeed/.gitignore | 6 +- CameraFeed/Dockerfile | 44 ++++++++++++--- CameraFeed/src/GetVideo.cpp | 11 +++- CameraFeed/src/JPEGMongoDBWriter.cpp | 82 ++++++++++++++++++++++++++++ CameraFeed/src/JPEGMongoDBWriter.h | 30 ++++++++++ CameraFeed/src/Makefile | 9 ++- CameraFeed/src/main.cpp | 6 +- 7 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 CameraFeed/src/JPEGMongoDBWriter.cpp create mode 100644 CameraFeed/src/JPEGMongoDBWriter.h diff --git a/CameraFeed/.gitignore b/CameraFeed/.gitignore index e4e5f6c8b2..a46310c784 100644 --- a/CameraFeed/.gitignore +++ b/CameraFeed/.gitignore @@ -1 +1,5 @@ -*~ \ No newline at end of file +*~ +*.o + +# Executable from src directory +AmcrestIPM-721S_StreamReader diff --git a/CameraFeed/Dockerfile b/CameraFeed/Dockerfile index be7974501e..86b05e63fd 100644 --- a/CameraFeed/Dockerfile +++ b/CameraFeed/Dockerfile @@ -6,25 +6,51 @@ FROM debian # curl). RUN apt-get -qq update -# Get g++, make -RUN apt-get install -y build-essential +############################## +# Libraries +############################## + +# Get g++, make, other build tools +RUN apt-get install -y build-essential automake autoconf libtool git checkinstall wget pkg-config curl libcurl3 libcurl4-gnutls-dev +# For cmake, see +# http://askubuntu.com/questions/610291/how-to-install-cmake-3-2-on-ubuntu-14-04 +RUN mkdir /usr/src/cmake +WORKDIR /usr/src/cmake +RUN wget https://cmake.org/files/v3.5/cmake-3.5.0.tar.gz +RUN tar xf cmake-3.5.0.tar.gz +WORKDIR cmake-3.5.0 +RUN ./configure && make && checkinstall # Packages for node.js service code -RUN apt-get install -y curl RUN curl -sL https://deb.nodesource.com/setup_5.x | /bin/bash - RUN apt-get install -y nodejs -# Packages used by C++ code to read camera stream -RUN apt-get install -y libcurl3 libcurl4-gnutls-dev +# MongoDB install +# See https://github.com/mongodb/mongo-cxx-driver/wiki/Quickstart-Guide-%28New-Driver%29 + +RUN mkdir /usr/src/mongodb +WORKDIR /usr/src/mongodb +RUN git clone -b r1.3 https://github.com/mongodb/mongo-c-driver +WORKDIR /usr/src/mongodb/mongo-c-driver +RUN ./autogen.sh && make && make install +WORKDIR /usr/src/mongodb +RUN git clone -b master https://github.com/mongodb/mongo-cxx-driver +WORKDIR /usr/src/mongodb/mongo-cxx-driver/build +RUN cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local .. +RUN make && make install + +############################## +# Custom Code +############################## # Brind in CameraFeed source RUN mkdir /usr/src/CameraFeed WORKDIR /usr/src/CameraFeed # Manual copy, excludes Dockerfile, emacs tmp files -COPY *js . -COPY keys . -COPY src . -COPY *json . +COPY *js ./ +COPY keys ./ +COPY src src/ +COPY *json ./ RUN npm install diff --git a/CameraFeed/src/GetVideo.cpp b/CameraFeed/src/GetVideo.cpp index e017ca0a31..cab203c8a3 100644 --- a/CameraFeed/src/GetVideo.cpp +++ b/CameraFeed/src/GetVideo.cpp @@ -138,6 +138,7 @@ size_t GetVideo::writeCallback(char *ptr, size_t size, size_t nmemb) } else if (ReadState::JPEG == myReadState) { + // TODO: passes non-jpeg data to callback when using debug auto remainingBytes = nmemb - ii; if (remainingBytes > myJPEGSize) { @@ -149,7 +150,15 @@ size_t GetVideo::writeCallback(char *ptr, size_t size, size_t nmemb) ii += remainingBytes; if (myJPEGSize == 0) { - myJPEGHandler.handleJPEG(myJPEGData.get(), myJPEGBytesSaved); + try + { + myJPEGHandler.handleJPEG(myJPEGData.get(), myJPEGBytesSaved); + } + catch (const std::exception &exception) + { + std::cerr << exception.what() << std::endl; + return size + 1; // Signal to curl that something went amiss. + } myReadState = ReadState::Trailer; } } diff --git a/CameraFeed/src/JPEGMongoDBWriter.cpp b/CameraFeed/src/JPEGMongoDBWriter.cpp new file mode 100644 index 0000000000..bba8ba5a6c --- /dev/null +++ b/CameraFeed/src/JPEGMongoDBWriter.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "JPEGMongoDBWriter.h" + +JPEGMongoDBWriter::JPEGMongoDBWriter(const std::string &theURI, int theDebug) : + myInstance{}, + myClient{mongocxx::uri{theURI}}, + myCollection{myClient["CSCI5799"]["CameraFeed"]}, + myDebug(theDebug) +{ +} + +void JPEGMongoDBWriter::handleJPEG(const char *theJPEG, size_t theSize) +{ + // From mongo shell + // > use CSCI5799 + // > show collections + // > db.CameraFeed.find() # list all JPEGs + // > db.CameraFeed.help() # lists commands + // > db.CameraFeed.deleteMany({}) # delete all documents in collection + + auto doc = bsoncxx::builder::basic::document{}; + // TODO: need username + doc.append(bsoncxx::builder::basic::kvp("user", + "dummyUser")); + // TODO: need camera id + doc.append(bsoncxx::builder::basic::kvp("camera_id", + "Camera123")); + + struct timeval currentTime; + gettimeofday(¤tTime, 0); + + doc.append(bsoncxx::builder::basic::kvp("unix_time", + [&](bsoncxx::builder::basic::sub_document subdoc) + { + subdoc.append( + bsoncxx::builder::basic::kvp( + "seconds", bsoncxx::types::b_int64{currentTime.tv_sec})); + subdoc.append( + bsoncxx::builder::basic::kvp( + "microseconds", + bsoncxx::types::b_int64{currentTime.tv_usec})); + })); + + doc.append(bsoncxx::builder::basic::kvp( + "jpeg", + bsoncxx::types::b_binary{ + bsoncxx::binary_sub_type::k_binary, + static_cast(theSize), // compiler warning + reinterpret_cast(theJPEG) + })); + + try + { + auto insertStatus = myCollection.insert_one(std::move(doc.view())); + if (myDebug > 2) + { + std::cout << "Inserted value" << std::endl; + if (insertStatus->inserted_id().type() == bsoncxx::type::k_oid) + { + std::cout << " id: " + << insertStatus->inserted_id().get_oid().value.to_string() + << std::endl; + } + } + } + catch (const mongocxx::exception &exception) + { + std::string error{"Error inserting JPEG into MongoDB: "}; + error += exception.what(); + throw std::runtime_error(error); + } +} diff --git a/CameraFeed/src/JPEGMongoDBWriter.h b/CameraFeed/src/JPEGMongoDBWriter.h new file mode 100644 index 0000000000..baf5205463 --- /dev/null +++ b/CameraFeed/src/JPEGMongoDBWriter.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include +#include +#include + +#include "JPEGHandler.h" + +class JPEGMongoDBWriter : public JPEGHandler +{ + public: + + JPEGMongoDBWriter() = delete; + + JPEGMongoDBWriter(const std::string &theURI, int theDebug); + + ~JPEGMongoDBWriter() = default; + + virtual void handleJPEG(const char *theJPEG, size_t theSize) override; + + private: + + mongocxx::instance myInstance; + mongocxx::client myClient; + mongocxx::collection myCollection; + const int myDebug; +}; + diff --git a/CameraFeed/src/Makefile b/CameraFeed/src/Makefile index 91fb730434..314b4c7785 100644 --- a/CameraFeed/src/Makefile +++ b/CameraFeed/src/Makefile @@ -3,6 +3,7 @@ SRCS := CommandLine.cpp \ CurlInit.cpp \ GetVideo.cpp \ JPEGFileWriter.cpp \ + JPEGMongoDBWriter.cpp \ main.cpp EXE := AmcrestIPM-721S_StreamReader @@ -10,16 +11,18 @@ EXE := AmcrestIPM-721S_StreamReader MAKEFLAGS := --no-print-directory DEPEND_FILE := .dependlist -# TODO: need to install libcurl4-gnutls-dev package CURL_C_FLAGS := $(shell curl-config --cflags) CURL_LD_FLAGS := $(shell curl-config --libs) +MONGO_C_FLAGS := $(shell pkg-config --cflags libmongocxx) +MONGO_LD_FLAGS := $(shell pkg-config --libs libmongocxx) + CC := g++ -CFLAGS := --std=c++11 -g -Wall $(CURL_C_FLAGS) $(INC_DIRS) +CFLAGS := --std=c++11 -g -Wall $(CURL_C_FLAGS) $(MONGO_C_FLAGS) $(INC_DIRS) LD := g++ LDFLAGS := -LIBS := $(CURL_LD_FLAGS) +LIBS := $(CURL_LD_FLAGS) $(MONGO_LD_FLAGS) OBJS := $(SRCS:%.cpp=%.o) diff --git a/CameraFeed/src/main.cpp b/CameraFeed/src/main.cpp index 8d9d0b45e0..4e1ddbc4c7 100644 --- a/CameraFeed/src/main.cpp +++ b/CameraFeed/src/main.cpp @@ -6,6 +6,7 @@ #include "CommandLine.h" #include "GetVideo.h" #include "JPEGFileWriter.h" +#include "JPEGMongoDBWriter.h" //http://stackoverflow.com/questions/12797647/ffmpeg-how-to-mux-mjpeg-encoded-data-into-mp4-or-avi-container-c //http://video.stackexchange.com/questions/7903/how-to-losslessly-encode-a-jpg-image-sequence-to-a-video-in-ffmpeg @@ -14,7 +15,10 @@ int main(int argc, char **argv) try { CommandLine commandLine(argc, argv); - JPEGFileWriter jpegWriter("/tmp/jpegs", ""); + // JPEGFileWriter jpegWriter("/tmp/jpegs", ""); + // TODO: need actual MongoDB URI + JPEGMongoDBWriter jpegWriter("mongodb://localhost:27017", + commandLine.getDebug()); GetVideo getVideo(commandLine.getURI(), commandLine.getUserName(), commandLine.getPassword(), jpegWriter, commandLine.getDebug()); From 6f83d1f083a073a4b91c12a3e10213e892713b25 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 26 Mar 2016 17:17:09 -0600 Subject: [PATCH 12/81] cleanup --- server/webfront/README.md | 52 ++++++++++++++++++------------------- server/webfront/webfront.go | 32 +++++++++++------------ 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/server/webfront/README.md b/server/webfront/README.md index 0b1f7d63ff..1409038333 100644 --- a/server/webfront/README.md +++ b/server/webfront/README.md @@ -19,7 +19,7 @@ No restart is needed to re-read the rule file and apply; within 60 seconds of a ``go run webfront.go -rules=rules.json -https=:9000 -https_cert=server.pem -https_key=server.key``` (or compile a binary, and run that) - + * To get a token: ``curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}'`` @@ -29,36 +29,36 @@ No restart is needed to re-read the rule file and apply; within 60 seconds of a ``{"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}`` Example: - ``` -[jvd@laika webfront (master *=)]$ curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}' -* Trying ::1... -* Connected to localhost (::1) port 9000 (#0) -* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 -* Server certificate: CU -> POST /login HTTP/1.1 -> Host: localhost:9000 -> User-Agent: curl/7.43.0 -> Accept: */* -> Content-Type:application/json -> Content-Length: 39 -> -* upload completely sent off: 39 out of 39 bytes -< HTTP/1.1 200 OK -< Content-Type: application/json -< Date: Thu, 24 Mar 2016 23:30:19 GMT -< Content-Length: 157 -< -* Connection #0 to host localhost left intact -{"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}[jvd@laika webfront (master *=)]$ - ``` + `` + [jvd@laika webfront (master *=)]$ curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}' + * Trying ::1... + * Connected to localhost (::1) port 9000 (#0) + * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + * Server certificate: CU + > POST /login HTTP/1.1 + > Host: localhost:9000 + > User-Agent: curl/7.43.0 + > Accept: */* + > Content-Type:application/json + > Content-Length: 39 + > + * upload completely sent off: 39 out of 39 bytes + < HTTP/1.1 200 OK + < Content-Type: application/json + < Date: Thu, 24 Mar 2016 23:30:19 GMT + < Content-Length: 157 + < + * Connection #0 to host localhost left intact + {"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}[jvd@laika webfront (master *=)]$ + `` * To use a token: ``curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r`` Example: - ``` - [jvd@laika webfront (master *%=)]$ curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r + `` + [jvd@laika webfront (master *%=)]$ curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r * Trying ::1... * Connected to localhost (::1) port 9000 (#0) * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 @@ -95,4 +95,4 @@ No restart is needed to re-read the rule file and apply; within 60 seconds of a < * Connection #0 to host localhost left intact [jvd@laika webfront (master=)]$ -``` \ No newline at end of file + `` \ No newline at end of file diff --git a/server/webfront/webfront.go b/server/webfront/webfront.go index 863c650b4c..8eac72b175 100644 --- a/server/webfront/webfront.go +++ b/server/webfront/webfront.go @@ -25,6 +25,22 @@ type TokenResponse struct { Token string } +// Server implements an http.Handler that acts as a reverse proxy +type Server struct { + mu sync.RWMutex // guards the fields below + last time.Time + rules []*Rule +} + +// Rule represents a rule in a configuration file. +type Rule struct { + Host string // to match against request Host header + Path string // to match against a path (start) + Forward string // reverse proxy map-to + + handler http.Handler +} + type loginJson struct { User string `json:"user"` Password string `json:"password"` @@ -51,22 +67,6 @@ func main() { http.ListenAndServeTLS(*httpsAddr, *certFile, *keyFile, s) } -// Server implements an http.Handler that acts as a reverse proxy -type Server struct { - mu sync.RWMutex // guards the fields below - last time.Time - rules []*Rule -} - -// Rule represents a rule in a configuration file. -type Rule struct { - Host string // to match against request Host header - Path string // to match against a path (start) - Forward string // reverse proxy map-to - - handler http.Handler -} - func validateToken(tokenString string) (*jwt.Token, error) { tokenString = strings.Replace(tokenString, "Bearer ", "", 1) From 494b25e59aa2c65a4fd6d4efec4b6025de742cb1 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 26 Mar 2016 17:17:40 -0600 Subject: [PATCH 13/81] moar cleanup --- server/webfront/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/webfront/README.md b/server/webfront/README.md index 1409038333..976ce8846a 100644 --- a/server/webfront/README.md +++ b/server/webfront/README.md @@ -1,6 +1,6 @@ This application - webfront is a reverse proxy written in go that can front any number of microservices. It uses a rules file to map from requested host/path to microservice host/port/path. Example rule file: - ``` + `` [ {"Host": "local.com", "Path" : "/8001", "Forward": "localhost:8001"}, {"Host": "local.com", "Path" : "/8002", "Forward": "localhost:8002"}, @@ -10,7 +10,7 @@ This application - webfront is a reverse proxy written in go that can front any {"Host": "local.com", "Path" : "/8006", "Forward": "localhost:8006"}, {"Host": "local.com", "Path" : "/8007", "Forward": "localhost:8007"} ] - ``` + `` No restart is needed to re-read the rule file and apply; within 60 seconds of a change in the file, it will pick up the new mappings. From 042db70747654d78e9ce1a620de0542934525e94 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 26 Mar 2016 17:20:26 -0600 Subject: [PATCH 14/81] markdown.... --- server/webfront/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/server/webfront/README.md b/server/webfront/README.md index 976ce8846a..52799b1fb0 100644 --- a/server/webfront/README.md +++ b/server/webfront/README.md @@ -1,6 +1,6 @@ This application - webfront is a reverse proxy written in go that can front any number of microservices. It uses a rules file to map from requested host/path to microservice host/port/path. Example rule file: - `` + [ {"Host": "local.com", "Path" : "/8001", "Forward": "localhost:8001"}, {"Host": "local.com", "Path" : "/8002", "Forward": "localhost:8002"}, @@ -10,26 +10,26 @@ This application - webfront is a reverse proxy written in go that can front any {"Host": "local.com", "Path" : "/8006", "Forward": "localhost:8006"}, {"Host": "local.com", "Path" : "/8007", "Forward": "localhost:8007"} ] - `` + No restart is needed to re-read the rule file and apply; within 60 seconds of a change in the file, it will pick up the new mappings. * To run - ``go run webfront.go -rules=rules.json -https=:9000 -https_cert=server.pem -https_key=server.key``` + go run webfront.go -rules=rules.json -https=:9000 -https_cert=server.pem -https_key=server.key (or compile a binary, and run that) * To get a token: - ``curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}'`` + curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}' - in my case that returned: +in my case that returned: - ``{"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}`` + {"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}`` - Example: - `` + Example: + [jvd@laika webfront (master *=)]$ curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}' * Trying ::1... * Connected to localhost (::1) port 9000 (#0) @@ -50,14 +50,14 @@ No restart is needed to re-read the rule file and apply; within 60 seconds of a < * Connection #0 to host localhost left intact {"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}[jvd@laika webfront (master *=)]$ - `` + * To use a token: - ``curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r`` + curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r - Example: +Example: - `` + [jvd@laika webfront (master *%=)]$ curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r * Trying ::1... * Connected to localhost (::1) port 9000 (#0) @@ -95,4 +95,4 @@ No restart is needed to re-read the rule file and apply; within 60 seconds of a < * Connection #0 to host localhost left intact [jvd@laika webfront (master=)]$ - `` \ No newline at end of file + \ No newline at end of file From 26435e37a447707d7cdcfead80601c248a63de83 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 26 Mar 2016 17:22:25 -0600 Subject: [PATCH 15/81] markdown.... more --- server/webfront/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/webfront/README.md b/server/webfront/README.md index 52799b1fb0..f568a0df2a 100644 --- a/server/webfront/README.md +++ b/server/webfront/README.md @@ -1,7 +1,7 @@ This application - webfront is a reverse proxy written in go that can front any number of microservices. It uses a rules file to map from requested host/path to microservice host/port/path. Example rule file: - [ + [ {"Host": "local.com", "Path" : "/8001", "Forward": "localhost:8001"}, {"Host": "local.com", "Path" : "/8002", "Forward": "localhost:8002"}, {"Host": "local.com", "Path" : "/8003", "Forward": "localhost:8003"}, @@ -16,17 +16,17 @@ No restart is needed to re-read the rule file and apply; within 60 seconds of a * To run - go run webfront.go -rules=rules.json -https=:9000 -https_cert=server.pem -https_key=server.key + go run webfront.go -rules=rules.json -https=:9000 -https_cert=server.pem -https_key=server.key - (or compile a binary, and run that) +(or compile a binary, and run that) * To get a token: - curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}' + curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}' in my case that returned: - {"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}`` + {"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}`` Example: @@ -53,7 +53,7 @@ in my case that returned: * To use a token: - curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r + curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r Example: From 4120e37facdb60e0ef6798a94944a4e4ee85a224 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 26 Mar 2016 17:23:46 -0600 Subject: [PATCH 16/81] markdown.... more --- server/webfront/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/webfront/README.md b/server/webfront/README.md index f568a0df2a..0af0c08584 100644 --- a/server/webfront/README.md +++ b/server/webfront/README.md @@ -16,7 +16,7 @@ No restart is needed to re-read the rule file and apply; within 60 seconds of a * To run - go run webfront.go -rules=rules.json -https=:9000 -https_cert=server.pem -https_key=server.key + go run webfront.go -rules=rules.json -https=:9000 -https_cert=server.pem -https_key=server.key (or compile a binary, and run that) From 73f8663ab21e23215e912e20ac0cc11283858ae2 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 26 Mar 2016 17:25:00 -0600 Subject: [PATCH 17/81] markdown.... more --- server/webfront/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/webfront/README.md b/server/webfront/README.md index 0af0c08584..041a219698 100644 --- a/server/webfront/README.md +++ b/server/webfront/README.md @@ -14,13 +14,13 @@ This application - webfront is a reverse proxy written in go that can front any No restart is needed to re-read the rule file and apply; within 60 seconds of a change in the file, it will pick up the new mappings. -* To run +To run go run webfront.go -rules=rules.json -https=:9000 -https_cert=server.pem -https_key=server.key (or compile a binary, and run that) -* To get a token: +To get a token: curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}' From 67b2b0aa7f9872a698ebe63a30febedda20818a8 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sun, 27 Mar 2016 11:36:15 -0600 Subject: [PATCH 18/81] move one dir up --- {server/webfront => webfront}/.gitignore | 0 {server/webfront => webfront}/README.md | 0 {server/webfront => webfront}/rules.json | 0 {server/webfront => webfront}/simpleserver.go | 0 {server/webfront => webfront}/startfakes.sh | 0 {server/webfront => webfront}/webfront.go | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {server/webfront => webfront}/.gitignore (100%) rename {server/webfront => webfront}/README.md (100%) rename {server/webfront => webfront}/rules.json (100%) rename {server/webfront => webfront}/simpleserver.go (100%) rename {server/webfront => webfront}/startfakes.sh (100%) rename {server/webfront => webfront}/webfront.go (100%) diff --git a/server/webfront/.gitignore b/webfront/.gitignore similarity index 100% rename from server/webfront/.gitignore rename to webfront/.gitignore diff --git a/server/webfront/README.md b/webfront/README.md similarity index 100% rename from server/webfront/README.md rename to webfront/README.md diff --git a/server/webfront/rules.json b/webfront/rules.json similarity index 100% rename from server/webfront/rules.json rename to webfront/rules.json diff --git a/server/webfront/simpleserver.go b/webfront/simpleserver.go similarity index 100% rename from server/webfront/simpleserver.go rename to webfront/simpleserver.go diff --git a/server/webfront/startfakes.sh b/webfront/startfakes.sh similarity index 100% rename from server/webfront/startfakes.sh rename to webfront/startfakes.sh diff --git a/server/webfront/webfront.go b/webfront/webfront.go similarity index 100% rename from server/webfront/webfront.go rename to webfront/webfront.go From 095e46a3e2edfd1f62052c3b5321fa1dd04ddcd1 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sun, 27 Mar 2016 13:26:45 -0600 Subject: [PATCH 19/81] trying some docker --- webfront/Dockerfile | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 webfront/Dockerfile diff --git a/webfront/Dockerfile b/webfront/Dockerfile new file mode 100644 index 0000000000..20f0be4658 --- /dev/null +++ b/webfront/Dockerfile @@ -0,0 +1,25 @@ +# Start from a Debian image with the latest version of Go installed +# and a workspace (GOPATH) configured at /go. +FROM golang + +# Copy the local package files to the container's workspace. +# Note: need to move to ~/go? +ADD . /go/src/github.com/rarenivar/project5799/webfront + +# Build the outyet command inside the container. +# (You may fetch or manage dependencies here, +# either manually or with a tool like "godep".) +RUN go get github.com/dgrijalva/jwt-go +#RUN ls -lR +WORKDIR ./src/github.com/rarenivar/project5799/webfront +#RUN cd ./src/github.com/rarenivar/project5799/webfront && export GOBIN=$GOPATH/bin && go install webfront.go +RUN export GOBIN=$GOPATH/bin && go install webfront.go +#RUN ls -l +#RUN go install webfront.go + +# Document that the service listens on port 9000. +EXPOSE 9000 + +# Run the outyet command by default when the container starts. +ENTRYPOINT /go/bin/webfront -rules=rules.json -https=:9000 -https_cert=server.pem -https_key=server.key + From c7520f7e8b22e5b04d0180ab17caddb78185b05a Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sun, 3 Apr 2016 13:54:40 -0600 Subject: [PATCH 20/81] start of DB schema --- db/create_tables_postgres | 172 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 db/create_tables_postgres diff --git a/db/create_tables_postgres b/db/create_tables_postgres new file mode 100644 index 0000000000..32fc960757 --- /dev/null +++ b/db/create_tables_postgres @@ -0,0 +1,172 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 9.4.5 +-- Dumped by pg_dump version 9.4.6 +-- Started on 2016-04-03 13:50:42 MDT + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SET check_function_bodies = false; +SET client_min_messages = warning; + +DROP DATABASE camera; +-- +-- TOC entry 2871 (class 1262 OID 16396) +-- Name: camera; Type: DATABASE; Schema: -; Owner: root +-- + +CREATE DATABASE camera WITH TEMPLATE = template0 ENCODING = 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8'; + + +ALTER DATABASE camera OWNER TO root; + +\connect camera + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SET check_function_bodies = false; +SET client_min_messages = warning; + +-- +-- TOC entry 7 (class 2615 OID 2200) +-- Name: public; Type: SCHEMA; Schema: -; Owner: root +-- + +CREATE SCHEMA public; + + +ALTER SCHEMA public OWNER TO root; + +-- +-- TOC entry 2872 (class 0 OID 0) +-- Dependencies: 7 +-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: root +-- + +COMMENT ON SCHEMA public IS 'standard public schema'; + + +-- +-- TOC entry 1 (class 3079 OID 12723) +-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: +-- + +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; + + +-- +-- TOC entry 2874 (class 0 OID 0) +-- Dependencies: 1 +-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; + + +SET search_path = public, pg_catalog; + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- TOC entry 174 (class 1259 OID 16405) +-- Name: cameras; Type: TABLE; Schema: public; Owner: root; Tablespace: +-- + +CREATE TABLE cameras ( + uuid text NOT NULL, + type text, + connect_string text, + owner text NOT NULL +); + + +ALTER TABLE cameras OWNER TO root; + +-- +-- TOC entry 173 (class 1259 OID 16397) +-- Name: users; Type: TABLE; Schema: public; Owner: root; Tablespace: +-- + +CREATE TABLE users ( + username text NOT NULL, + first_name text NOT NULL, + last_name text NOT NULL, + password text NOT NULL +); + + +ALTER TABLE users OWNER TO root; + +-- +-- TOC entry 2866 (class 0 OID 16405) +-- Dependencies: 174 +-- Data for Name: cameras; Type: TABLE DATA; Schema: public; Owner: root +-- + +COPY cameras (uuid, type, connect_string, owner) FROM stdin; +\. + + +-- +-- TOC entry 2865 (class 0 OID 16397) +-- Dependencies: 173 +-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: root +-- + +COPY users (username, first_name, last_name, password) FROM stdin; +\. + + +-- +-- TOC entry 2752 (class 2606 OID 16404) +-- Name: username; Type: CONSTRAINT; Schema: public; Owner: root; Tablespace: +-- + +ALTER TABLE ONLY users + ADD CONSTRAINT username PRIMARY KEY (username); + + +-- +-- TOC entry 2754 (class 2606 OID 16412) +-- Name: uuid; Type: CONSTRAINT; Schema: public; Owner: root; Tablespace: +-- + +ALTER TABLE ONLY cameras + ADD CONSTRAINT uuid PRIMARY KEY (uuid); + + +-- +-- TOC entry 2755 (class 2606 OID 16413) +-- Name: owner_fk; Type: FK CONSTRAINT; Schema: public; Owner: root +-- + +ALTER TABLE ONLY cameras + ADD CONSTRAINT owner_fk FOREIGN KEY (owner) REFERENCES users(username); + + +-- +-- TOC entry 2873 (class 0 OID 0) +-- Dependencies: 7 +-- Name: public; Type: ACL; Schema: -; Owner: root +-- + +REVOKE ALL ON SCHEMA public FROM PUBLIC; +REVOKE ALL ON SCHEMA public FROM root; +GRANT ALL ON SCHEMA public TO root; +GRANT ALL ON SCHEMA public TO PUBLIC; + + +-- Completed on 2016-04-03 13:50:47 MDT + +-- +-- PostgreSQL database dump complete +-- + From 20e81a0197b630f3a93c2e4cace66298b4b6b417 Mon Sep 17 00:00:00 2001 From: Ramiro Arenivar Date: Mon, 4 Apr 2016 00:54:08 -0600 Subject: [PATCH 21/81] deleted local login feature, login now done via external API. New helpers and files containing the test APIs. New file defining api calls, data collections, etc --- camera5799/.meteor/packages | 2 + camera5799/.meteor/versions | 1 + .../client/templates/includes/header.html | 30 +++-- .../client/templates/includes/header.js | 14 ++ .../templates/views/browse_cameras.html | 3 + .../client/templates/views/browse_cameras.js | 28 ++++ .../client/templates/views/home_page.html | 125 +++++++++++++++++- .../client/templates/views/home_page.js | 32 +++++ camera5799/lib/data_collections.js | 1 + camera5799/server/api_calls.js | 81 ++++++++++++ 10 files changed, 298 insertions(+), 19 deletions(-) mode change 100755 => 100644 camera5799/.meteor/packages create mode 100644 camera5799/client/templates/views/browse_cameras.js create mode 100644 camera5799/client/templates/views/home_page.js create mode 100644 camera5799/lib/data_collections.js create mode 100644 camera5799/server/api_calls.js diff --git a/camera5799/.meteor/packages b/camera5799/.meteor/packages old mode 100755 new mode 100644 index f11a5ed229..e52b3b7eee --- a/camera5799/.meteor/packages +++ b/camera5799/.meteor/packages @@ -24,3 +24,5 @@ accounts-password ian:accounts-ui-bootstrap-3 check audit-argument-checks +http +autopublish diff --git a/camera5799/.meteor/versions b/camera5799/.meteor/versions index 98cdcbad6b..82f26a1fee 100644 --- a/camera5799/.meteor/versions +++ b/camera5799/.meteor/versions @@ -2,6 +2,7 @@ accounts-base@1.2.2 accounts-password@1.1.4 anti:i18n@0.4.3 audit-argument-checks@1.0.4 +autopublish@1.0.4 autoupdate@1.2.4 babel-compiler@5.8.24_1 babel-runtime@0.1.4 diff --git a/camera5799/client/templates/includes/header.html b/camera5799/client/templates/includes/header.html index f5db1d8090..2212eb1550 100755 --- a/camera5799/client/templates/includes/header.html +++ b/camera5799/client/templates/includes/header.html @@ -11,20 +11,22 @@ diff --git a/camera5799/client/templates/includes/header.js b/camera5799/client/templates/includes/header.js index 3bd9c83ef9..54d0508624 100755 --- a/camera5799/client/templates/includes/header.js +++ b/camera5799/client/templates/includes/header.js @@ -1,4 +1,5 @@ Template.header.helpers({ + activeRouteClass: function(/* route names */) { var args = Array.prototype.slice.call(arguments, 0); args.pop(); @@ -8,5 +9,18 @@ Template.header.helpers({ }); return active && 'active'; + }, + + login_response: function() { + return Session.get('login_response'); + } +}); + +Template.header.events({ + 'click #logout_button': function(e) { + e.preventDefault(); + localStorage.removeItem('login_response'); + Session.set('login_response', null); + Router.go('homePage'); } }); \ No newline at end of file diff --git a/camera5799/client/templates/views/browse_cameras.html b/camera5799/client/templates/views/browse_cameras.html index 86ba9fe8e4..afb4034080 100644 --- a/camera5799/client/templates/views/browse_cameras.html +++ b/camera5799/client/templates/views/browse_cameras.html @@ -4,6 +4,9 @@ Registered Cameras + {{#each availableCameras}} + {{cameraName}} + {{/each}} Living room camera Work camera diff --git a/camera5799/client/templates/views/browse_cameras.js b/camera5799/client/templates/views/browse_cameras.js new file mode 100644 index 0000000000..19f1343b3c --- /dev/null +++ b/camera5799/client/templates/views/browse_cameras.js @@ -0,0 +1,28 @@ +Template.browseCameras.helpers({ + availableCameras: function() { + //AvailableCameras.insert({cameraName: "livingroom", date: "jan 1st 2016"}); + return AvailableCameras.find(); + } +}); + +Template.browseCameras.onCreated(function() { + alert("on created method fired!"); + + var login_data = Session.get('login_response'); + var token = null; + if (login_data.hasOwnProperty('token')) { + token = login_data.token; + } + + Meteor.call('getCameras', token, function(err, res) { + if (err) { + console.log('getCameras error... ', err.message); + } else { + + console.log("gerCameras response... ", res.content); + } + return res; + + }); + +}); \ No newline at end of file diff --git a/camera5799/client/templates/views/home_page.html b/camera5799/client/templates/views/home_page.html index 7153233d53..8f30eaa855 100644 --- a/camera5799/client/templates/views/home_page.html +++ b/camera5799/client/templates/views/home_page.html @@ -1,12 +1,127 @@ \ No newline at end of file diff --git a/camera5799/client/templates/views/home_page.js b/camera5799/client/templates/views/home_page.js new file mode 100644 index 0000000000..8714697ded --- /dev/null +++ b/camera5799/client/templates/views/home_page.js @@ -0,0 +1,32 @@ +Meteor.startup(function() { + Session.set('login_response', JSON.parse(localStorage.getItem('login_response'))); +}); + +Template.homePage.helpers({ + login_response: function() { + return Session.get('login_response'); + } +}); + +Template.homePage.events({ + 'click #btn-login': function (evt, tpl) { + + var username = tpl.find('input#login-username').value; + var password = tpl.find('input#login-password').value; + + Meteor.call('loginCall', username, password, function(err, res) { + console.log("client response... ", res); + if (err) { + console.log("error from client"); + localStorage.setItem('login_response', JSON.stringify({error: err})); + } else { + console.log("the response from client! ", res); + localStorage.setItem('login_response', JSON.stringify({token: res, username: username})); + } + Session.set('login_response', JSON.parse(localStorage.getItem('login_response'))); + Router.go('browseCameras'); + return res; + + }); + } +}); \ No newline at end of file diff --git a/camera5799/lib/data_collections.js b/camera5799/lib/data_collections.js new file mode 100644 index 0000000000..f48817e448 --- /dev/null +++ b/camera5799/lib/data_collections.js @@ -0,0 +1 @@ +AvailableCameras = new Mongo.Collection('availableCameras'); \ No newline at end of file diff --git a/camera5799/server/api_calls.js b/camera5799/server/api_calls.js new file mode 100644 index 0000000000..73aeb26eea --- /dev/null +++ b/camera5799/server/api_calls.js @@ -0,0 +1,81 @@ +// needed for self signed certificate +// link... https://github.com/meteor/meteor/issues/2866 +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; +Future = Npm.require('fibers/future'); + +Meteor.methods({ + + // The method expects a valid IPv4 address + 'loginCall': function (username, password) { + // Construct the API URL + var myFuture = new Future(); + check(username, String); + check(password, String); + var apiUrl = 'https://ec2-52-37-126-44.us-west-2.compute.amazonaws.com:9000/login'; + var response = null; + // query the API + //var response = HTTP.get(apiUrl).data; + HTTP.call("POST", apiUrl, + {data: {"username": username, "password": password}}, + function (error, result) { + if (!error) { + var tokenValue = null; + if (result.hasOwnProperty('content')) { + tokenValue = result.content; + tokenValue = JSON.parse(tokenValue); + if (tokenValue.hasOwnProperty('Token')) { + tokenValue = tokenValue.Token; + myFuture.return(tokenValue); + } + } + } else { + console.log("error ===> ", error.toString()); + myFuture.throw(error); + } + }); + return myFuture.wait(); + }, + + 'getCameras': function (token) { + var myFuture = new Future(); + check(token, String); + token = 'Bearer ' + token; + var apiURL = "https://ec2-52-37-126-44.us-west-2.compute.amazonaws.com:9000/8001/r"; + + HTTP.call("POST", apiURL, + { headers: { 'Authorization': token} }, + function (error, result) { + //result.statuscode + if (!error) { + console.log("11getCameras result content..." + result.content); + AvailableCameras.insert({cameraName: "livingroom", date: "jan 1st 2016"}); + console.log("the count is ", AvailableCameras.find().count()); + myFuture.return(result.content); + } else { + console.log("error ===> ", error.toString()); + myFuture.throw(error); + } + }); + return myFuture.wait(); + }, + + 'getVideos': function (token) { + + }, + + 'getLiveFeed': function (token) { + + }, + + 'saveFeed': function (token) { + + }, + + 'controlFeed': function (token) { + + }, + + 'registerCamera': function (token) { + + } +}); \ No newline at end of file From dd48aa21dc42b27519abc00f9233d25a1a7af06b Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 9 Apr 2016 10:05:34 -0600 Subject: [PATCH 22/81] user / register start --- user/.gitignore | 1 + user/README.md | 8 +++ user/main.go | 147 +++++++++++++++++++++++++++++++++++++++++++ webfront/webfront.go | 1 + 4 files changed, 157 insertions(+) create mode 100644 user/.gitignore create mode 100644 user/README.md create mode 100644 user/main.go diff --git a/user/.gitignore b/user/.gitignore new file mode 100644 index 0000000000..143c64680f --- /dev/null +++ b/user/.gitignore @@ -0,0 +1 @@ +*.config diff --git a/user/README.md b/user/README.md new file mode 100644 index 0000000000..a11dae48e7 --- /dev/null +++ b/user/README.md @@ -0,0 +1,8 @@ +* `POST /` + Create a new user +* `GET /{id}` + Read user with id +* `PUT /{id}` + Update user with id +* `DELETE /{id}` + Delete user with id diff --git a/user/main.go b/user/main.go new file mode 100644 index 0000000000..f48fb7c1b0 --- /dev/null +++ b/user/main.go @@ -0,0 +1,147 @@ +package main + +import ( + // "encoding/gob" + "encoding/json" + "fmt" + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" + // null "gopkg.in/guregu/null.v3" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "strings" +) + +// Config holds the configuration of the server. +type Config struct { + DbName string `json:"dbName"` + DbUser string `json:"dbUser"` + DbPassword string `json:"dbPassword"` + DbServer string `json:"dbServer,omitempty"` + DbPort uint `json:"dbPort,omitempty"` + ListenerPort string `json:"listenerPort"` +} + +type User struct { + Username string `db:"username" json:"username"` + FirstName string `db:"first_name" json:"firstName"` + LastName string `db:"last_name" json:"lastName"` + Password string `db:"password" json:"Password"` +} + +var db *sqlx.DB // global and simple + +func printUsage() { + exampleConfig := `{ + "dbName":"my-db", + "dbUser":"my-user", + "dbPassword":"secret", + "dbServer":"localhost", + "dbPort":5432, + "listenerPort":"8080" +}` + log.Println("Usage: " + path.Base(os.Args[0]) + " configfile") + log.Println("") + log.Println("Example config file:") + log.Println(exampleConfig) +} + +func main() { + if len(os.Args) < 2 { + printUsage() + return + } + + log.SetOutput(os.Stdout) + + file, err := os.Open(os.Args[1]) + if err != nil { + log.Println("Error opening config file:", err) + return + } + decoder := json.NewDecoder(file) + config := Config{} + err = decoder.Decode(&config) + if err != nil { + log.Println("Error reading config file:", err) + return + } + + // gob.Register(auth.SessionUser{}) // this is needed to pass the SessionUser struct around in the gorilla session. + + log.Println(config.DbUser, config.DbPassword, config.DbName, config.DbServer, config.DbPort) + db, err = InitializeDatabase(config.DbUser, config.DbPassword, config.DbName, config.DbServer, config.DbPort) + if err != nil { + log.Println("Error initializing database:", err) + return + } + + var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile) + Logger.Printf("Starting server on port " + config.ListenerPort + "...") + + // err = http.ListenAndServe(":"+config.ListenerPort, handlers.CombinedLoggingHandler(os.Stdout, routes.CreateRouter(dbb))) + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) + + if err != nil { + log.Println(err) + } +} + +func InitializeDatabase(username, password, dbname, server string, port uint) (*sqlx.DB, error) { + connString := fmt.Sprintf("host=%s dbname=%s user=%s password=%s sslmode=disable", server, dbname, username, password) + + db, err := sqlx.Connect("postgres", connString) + if err != nil { + return nil, err + } + + return db, nil +} + +func handler(w http.ResponseWriter, r *http.Request) { + + if r.Method == "GET" { + if r.URL.Path == "/" { + // TODO return list + } else { + username := strings.Replace(r.URL.Path, "/", "", 1) + userlist := []User{} + argument := User{} + argument.Username = username + stmt, err := db.PrepareNamed("SELECT * FROM users WHERE username=:username") + err = stmt.Select(&userlist, argument) + if err != nil { + log.Println(err) + } + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) + enc.Encode(userlist) + } + } else if r.Method == "POST" { + var u User + body, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Println(err) + } + err = json.Unmarshal(body, &u) + if err != nil { + log.Println(err) + // TODO return error. + } + sqlString := "INSERT INTO users (username, last_name, first_name, password) VALUES (:username, :last_name, :first_name, :password)" + result, err := db.NamedExec(sqlString, u) + if err != nil { + log.Println(err) + // TODO return error. + } + fmt.Fprintf(w, "Done! (%s)", result) + } else if r.Method == "PUT" { + + } else if r.Method == "DELETE" { + + } +} diff --git a/webfront/webfront.go b/webfront/webfront.go index 8eac72b175..bc7faf8fc8 100644 --- a/webfront/webfront.go +++ b/webfront/webfront.go @@ -55,6 +55,7 @@ var ( ) func main() { + log.Println("Starting webfront...") flag.Parse() s, err := NewServer(*ruleFile, *pollInterval) if err != nil { From 4abb9ea602d8966cf39a4c5baf4ad1d5cf037cf0 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 9 Apr 2016 10:06:26 -0600 Subject: [PATCH 23/81] Update README.md --- user/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/user/README.md b/user/README.md index a11dae48e7..f0359e0a2d 100644 --- a/user/README.md +++ b/user/README.md @@ -1,8 +1,12 @@ * `POST /` + Create a new user * `GET /{id}` + Read user with id * `PUT /{id}` + Update user with id * `DELETE /{id}` + Delete user with id From 4a3565f65e2a878c0bbedfa9de29b7e3e78fe4c9 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 9 Apr 2016 10:07:43 -0600 Subject: [PATCH 24/81] Update README.md --- user/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/user/README.md b/user/README.md index f0359e0a2d..a7e5cf4f69 100644 --- a/user/README.md +++ b/user/README.md @@ -1,6 +1,11 @@ * `POST /` Create a new user + + ``` + curl -Lkvs -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' 'http://localhost:8080/' -d'{"username":"jvd", "firstName":"vandoorn", "password": "secret"}' + ``` + * `GET /{id}` Read user with id From 3724a60f00e422570ac125d3c1f292fcb5bead78 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 9 Apr 2016 11:32:18 -0600 Subject: [PATCH 25/81] checkpoint --- db/create_tables_postgres | 3 ++ user/README.md | 10 ++++++ user/main.go | 71 +++++++++++++++++++++++++++++++++------ 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/db/create_tables_postgres b/db/create_tables_postgres index 32fc960757..71abc26515 100644 --- a/db/create_tables_postgres +++ b/db/create_tables_postgres @@ -163,6 +163,9 @@ REVOKE ALL ON SCHEMA public FROM root; GRANT ALL ON SCHEMA public TO root; GRANT ALL ON SCHEMA public TO PUBLIC; +GRANT ALL ON TABLE users TO camuser; +GRANT ALL ON TABLE cameras TO camuser; + -- Completed on 2016-04-03 13:50:47 MDT diff --git a/user/README.md b/user/README.md index a7e5cf4f69..54d1b36cf6 100644 --- a/user/README.md +++ b/user/README.md @@ -5,6 +5,16 @@ ``` curl -Lkvs -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' 'http://localhost:8080/' -d'{"username":"jvd", "firstName":"vandoorn", "password": "secret"}' ``` + + example JSON payload: + + ``` + { + "username": "jvd", + "firstName": "vandoorn", + "password": "secret" + } + ``` * `GET /{id}` diff --git a/user/main.go b/user/main.go index f48fb7c1b0..174fe0a7f6 100644 --- a/user/main.go +++ b/user/main.go @@ -70,9 +70,6 @@ func main() { return } - // gob.Register(auth.SessionUser{}) // this is needed to pass the SessionUser struct around in the gorilla session. - - log.Println(config.DbUser, config.DbPassword, config.DbName, config.DbServer, config.DbPort) db, err = InitializeDatabase(config.DbUser, config.DbPassword, config.DbName, config.DbServer, config.DbPort) if err != nil { log.Println("Error initializing database:", err) @@ -82,7 +79,6 @@ func main() { var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile) Logger.Printf("Starting server on port " + config.ListenerPort + "...") - // err = http.ListenAndServe(":"+config.ListenerPort, handlers.CombinedLoggingHandler(os.Stdout, routes.CreateRouter(dbb))) http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) @@ -102,11 +98,23 @@ func InitializeDatabase(username, password, dbname, server string, port uint) (* return db, nil } +func retErr(w http.ResponseWriter, status int) { + w.WriteHeader(status) +} + func handler(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.URL.Scheme, r.Host, r.URL.RequestURI()) if r.Method == "GET" { if r.URL.Path == "/" { - // TODO return list + userlist := []User{} + err := db.Select(&userlist, "SELECT * FROM users") + if err != nil { + log.Println(err) + } + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) + enc.Encode(userlist) } else { username := strings.Replace(r.URL.Path, "/", "", 1) userlist := []User{} @@ -116,10 +124,16 @@ func handler(w http.ResponseWriter, r *http.Request) { err = stmt.Select(&userlist, argument) if err != nil { log.Println(err) + retErr(w, http.StatusInternalServerError) + return + } + if len(userlist) == 0 { + retErr(w, http.StatusNotFound) + return } w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) - enc.Encode(userlist) + enc.Encode(userlist[0]) } } else if r.Method == "POST" { var u User @@ -130,18 +144,53 @@ func handler(w http.ResponseWriter, r *http.Request) { err = json.Unmarshal(body, &u) if err != nil { log.Println(err) - // TODO return error. + retErr(w, http.StatusInternalServerError) + return } + // TODO encrypt passwd before storing. sqlString := "INSERT INTO users (username, last_name, first_name, password) VALUES (:username, :last_name, :first_name, :password)" result, err := db.NamedExec(sqlString, u) if err != nil { log.Println(err) - // TODO return error. + retErr(w, http.StatusInternalServerError) + return } - fmt.Fprintf(w, "Done! (%s)", result) + rows, _ := result.RowsAffected() + fmt.Fprintf(w, "Done! (%s Rows Affected)", rows) } else if r.Method == "PUT" { - + var u User + body, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Println(err) + } + err = json.Unmarshal(body, &u) + if err != nil { + log.Println(err) + retErr(w, http.StatusInternalServerError) + return + } + u.Username = strings.Replace(r.URL.Path, "/", "", 1) // overwrite the username in the json, the path gets checked. + // TODO encrypt passwd before storing. + sqlString := "UPDATE users SET last_name=:last_name, first_name=:first_name, password=:password WHERE username=:username" + result, err := db.NamedExec(sqlString, u) + if err != nil { + log.Println(err) + retErr(w, http.StatusInternalServerError) + return + } + rows, _ := result.RowsAffected() + fmt.Fprintf(w, "Done! (%s Rows Affected)", rows) } else if r.Method == "DELETE" { - + argument := User{} + argument.Username = strings.Replace(r.URL.Path, "/", "", 1) + result, err := db.NamedExec("DELETE FROM users WHERE username=:username", argument) + if err != nil { + log.Println(err) + retErr(w, http.StatusInternalServerError) + return + } + rows, _ := result.RowsAffected() + fmt.Fprintf(w, "Done! (%s Rows Affected)", rows) } + retErr(w, http.StatusNotFound) } From 2ad51967287d91c437dd7d9592c761e7325c6ba9 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sat, 9 Apr 2016 13:00:53 -0600 Subject: [PATCH 26/81] https for all? --- auth/.gitignore | 1 + auth/main.go | 198 +++++++++++++++++++++++++++++++++++++++++++ auth/server.key | 27 ++++++ auth/server.pem | 21 +++++ webfront/rules.json | 13 ++- webfront/webfront.go | 90 +++++++++----------- 6 files changed, 291 insertions(+), 59 deletions(-) create mode 100644 auth/.gitignore create mode 100644 auth/main.go create mode 100644 auth/server.key create mode 100644 auth/server.pem diff --git a/auth/.gitignore b/auth/.gitignore new file mode 100644 index 0000000000..143c64680f --- /dev/null +++ b/auth/.gitignore @@ -0,0 +1 @@ +*.config diff --git a/auth/main.go b/auth/main.go new file mode 100644 index 0000000000..5bc748c6cd --- /dev/null +++ b/auth/main.go @@ -0,0 +1,198 @@ +package main + +import ( + // "encoding/gob" + "encoding/json" + "fmt" + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" + // null "gopkg.in/guregu/null.v3" + jwt "github.com/dgrijalva/jwt-go" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "strings" + "time" +) + +// Config holds the configuration of the server. +type Config struct { + DbName string `json:"dbName"` + DbUser string `json:"dbUser"` + DbPassword string `json:"dbPassword"` + DbServer string `json:"dbServer,omitempty"` + DbPort uint `json:"dbPort,omitempty"` + ListenerPort string `json:"listenerPort"` +} + +type User struct { + Username string `db:"username" json:"username"` + FirstName string `db:"first_name" json:"firstName,omitempty"` + LastName string `db:"last_name" json:"lastName,omitempty"` + Password string `db:"password" json:"Password"` +} + +type TokenResponse struct { + Token string +} + +var db *sqlx.DB // global and simple + +func printUsage() { + exampleConfig := `{ + "dbName":"my-db", + "dbUser":"my-user", + "dbPassword":"secret", + "dbServer":"localhost", + "dbPort":5432, + "listenerPort":"8080" +}` + log.Println("Usage: " + path.Base(os.Args[0]) + " configfile") + log.Println("") + log.Println("Example config file:") + log.Println(exampleConfig) +} + +func main() { + if len(os.Args) < 2 { + printUsage() + return + } + + log.SetOutput(os.Stdout) + + file, err := os.Open(os.Args[1]) + if err != nil { + log.Println("Error opening config file:", err) + return + } + decoder := json.NewDecoder(file) + config := Config{} + err = decoder.Decode(&config) + if err != nil { + log.Println("Error reading config file:", err) + return + } + + db, err = InitializeDatabase(config.DbUser, config.DbPassword, config.DbName, config.DbServer, config.DbPort) + if err != nil { + log.Println("Error initializing database:", err) + return + } + + var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile) + Logger.Printf("Starting server on port " + config.ListenerPort + "...") + + http.HandleFunc("/", handler) + // http.ListenAndServe(":8080", nil) + http.ListenAndServeTLS(":8080", "server.pem", "server.key", nil) + + if err != nil { + log.Println(err) + } +} +func validateToken(tokenString string) (*jwt.Token, error) { + + tokenString = strings.Replace(tokenString, "Bearer ", "", 1) + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Don't forget to validate the alg is what you expect: + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return []byte("CAmeRAFiveSevenNineNine"), nil + }) + + if err == nil && token.Valid { + log.Println("TOKEN IS GOOD -- user:", token.Claims["userid"], " role:", token.Claims["role"]) + } else { + log.Println("TOKEN IS BAD", err) + } + return token, err +} + +func InitializeDatabase(username, password, dbname, server string, port uint) (*sqlx.DB, error) { + connString := fmt.Sprintf("host=%s dbname=%s user=%s password=%s sslmode=disable", server, dbname, username, password) + + db, err := sqlx.Connect("postgres", connString) + if err != nil { + return nil, err + } + + return db, nil +} + +func retErr(w http.ResponseWriter, status int) { + w.WriteHeader(status) +} + +func handler(w http.ResponseWriter, r *http.Request) { + + log.Println(r.Method, r.URL.Scheme, r.Host, r.URL.RequestURI()) + if r.Method == "POST" { + var u User + userlist := []User{} + username := "" + password := "" + body, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Println("Error reading body: ", err.Error()) + http.Error(w, "Error reading body: "+err.Error(), http.StatusBadRequest) + return + } + // var lj loginJson + // log.Println(body) + err = json.Unmarshal(body, &u) + if err != nil { + log.Println("Error unmarshalling JSON: ", err.Error()) + http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) + return + } + // username = lj.User + // password = lj.Password + + stmt, err := db.PrepareNamed("SELECT * FROM users WHERE username=:username") + err = stmt.Select(&userlist, u) + if err != nil { + log.Println(err) + retErr(w, http.StatusInternalServerError) + return + } + if len(userlist) == 0 || userlist[0].Password != u.Password { + retErr(w, http.StatusUnauthorized) + return + } + + token := jwt.New(jwt.SigningMethodHS256) + token.Claims["User"] = username + token.Claims["Password"] = password + token.Claims["exp"] = time.Now().Add(time.Hour * 24).Unix() + tokenString, err := token.SignedString([]byte("CAmeRAFiveSevenNineNine")) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + js, err := json.Marshal(TokenResponse{Token: tokenString}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(js) + // var u User + // userlist := []User{} + // body, err := ioutil.ReadAll(r.Body) + // if err != nil { + // log.Println(err) + // } + // err = json.Unmarshal(body, &u) + // if err != nil { + // log.Println(err) + // retErr(w, http.StatusInternalServerError) + // return + // } + + } + retErr(w, http.StatusNotFound) +} diff --git a/auth/server.key b/auth/server.key new file mode 100644 index 0000000000..2729786cc5 --- /dev/null +++ b/auth/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEArgqw5G7wuuw50ly1lM9UIqzQAVX68LIcjTWDw0CpYpeaPB2P +opU0KBB1uh18kJbMl+UvNwxEtbfsk2cCnFkVhf/S26AJ6mGkfofw2pcfsUrHbicr +NK693N/EiWgBAfb9xeD9FWcDWZJ8UcPdB4O5O10Xewv0f3Dc9UMz3f9833TOvoPn +4cbkj/MDiebfTjqMZoRhUE3pKs4tf9wEC5m/QKKMDWVw/7oSXrgXxKwDemD10LBc +o0U6uANuQVMaqfJfn3TbqgfPBSc+Tjxs+L248dtL3GQ+06/lxZHOcFYQG3V2k/O1 +r9q9xBGDLJ0C4gkh0BJWMgOIErjM+vbvHj4EuwIDAQABAoIBAQCCo2G+RgwKsxB/ +97kQ90NXN9J2fjx7eaZ21EmQQTQj3loWb7YGOurIe/pydnmu6pKuHYkAUAQvRtyc +lEDgbPn7+FglJQ7sUNixZYcD/VX7tX7XDy2eQOAhMBINdfTzrZs1cjlmXEmmbY9F +rLh3UIe0qJ7cbM/c7THeI9CF/aWU8JK5EnlsQjHTsqXFh3bB4jZJMJbERALh/wb0 +aaYhzRUbuaVh4pwM02WjX/3Zd+D6mkiT3Jv6Vw7V/f1Gv5NIaAZVEvJdXdAZlXvo +qXSbuXZ7CxJnGOQZmLTnH8TThaT6CWnBjD2fIUL89T72nKoqwgvd9+kwbFR6UkIH +8rRE1brpAoGBANsTkskiey/nMLRsQNbOw2zlqQTxgGnOPF0CMTrrOjgVsDfvO/p3 +53hZtmL6YcdQqphXzupu+bDOa0cIwd4lCck8Q4fauq5CpYcEX6rohSa3qASBZrsI +Q/7O9UOcKb2y+7Q8fqSqP20d5pL7zDHXcb9wSuR6KPLr8WBFwNVQQvp9AoGBAMtg +BNs0vc2DmEd3C/ZZi0L6FE7vMvV/VS2BN+OaHpPTKrvt88S/316spmansfwUFqIC +DT0RCwTJ9HnQnPdOYSeoCkDm69jpUft9xTAR0s0E+JNL4a7O62m8Psbq9Cm8QK7O +WQQeiNBxQJer6FxO9IBSsoij2NRTFlC5lDAXK2mXAoGBALPyHOSXLQZI6MmaDZwj +k4P0FGPaFndSDmT7MwLJSjXdIrSfDe4K5RcG4XflX8E+shGWNoB7jR68PoPHXq6g +T6x6kqGZeTM/zQTdA9O3T/9gZWLmA0LnZCp9Rho1wFdwF62Q7xlEOfrfz4+1waDC +7Uyo6OtIqC/4u3gvc1gDqDBlAoGAFhhIbsg8FqfFU35LfmgcCRrCOhEiqCk9R8RS +HhgQWl8GtYQUh7uQuOho654saAaGrvMqHQhM+ig2t0VCyFtrkrNe6c4ssVQn4/q2 +AZLuI/f2SYpk6mjwinw5FcNQ43fgTx177kai9oJXUiMLC3xIc+iIHPAwDmE4e2yt +/MuHm+ECgYEAybHU3EjvHG9aCxhyYc4a3odES4Tz/R48N8OHejTxPhgUjilofU8h +r+UQTFrHCh0WQnVtadfex0x5/jYGU+jae6W0Tdvj4oa8imSMOil+biXaYcFFIWXr +JhPaaDt0V8GwpmeHCBj/6QOIIUDK83XqYwv7aDrqBMOFFl2QumrckQM= +-----END RSA PRIVATE KEY----- diff --git a/auth/server.pem b/auth/server.pem new file mode 100644 index 0000000000..2f47ad8bd1 --- /dev/null +++ b/auth/server.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDgzCCAmugAwIBAgIJAP9mv1xFYeiyMA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDTzEPMA0GA1UEBwwGRGVudmVyMQswCQYDVQQKDAJD +VTEeMBwGCSqGSIb3DQEJARYPanZkQGtudXRzZWwuY29tMB4XDTE2MDMwNTIwMjYy +NFoXDTI2MDMwMzIwMjYyNFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMQ8w +DQYDVQQHDAZEZW52ZXIxCzAJBgNVBAoMAkNVMR4wHAYJKoZIhvcNAQkBFg9qdmRA +a251dHNlbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCuCrDk +bvC67DnSXLWUz1QirNABVfrwshyNNYPDQKlil5o8HY+ilTQoEHW6HXyQlsyX5S83 +DES1t+yTZwKcWRWF/9LboAnqYaR+h/Dalx+xSsduJys0rr3c38SJaAEB9v3F4P0V +ZwNZknxRw90Hg7k7XRd7C/R/cNz1QzPd/3zfdM6+g+fhxuSP8wOJ5t9OOoxmhGFQ +Tekqzi1/3AQLmb9AoowNZXD/uhJeuBfErAN6YPXQsFyjRTq4A25BUxqp8l+fdNuq +B88FJz5OPGz4vbjx20vcZD7Tr+XFkc5wVhAbdXaT87Wv2r3EEYMsnQLiCSHQElYy +A4gSuMz69u8ePgS7AgMBAAGjUDBOMB0GA1UdDgQWBBRbyiGJ5h0Npd277VCQ6ePS +N8s9fDAfBgNVHSMEGDAWgBRbyiGJ5h0Npd277VCQ6ePSN8s9fDAMBgNVHRMEBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBSUalyrKENU24QxW9h3t3PbaxOQng8Gs8s +UIPJQh1Z2ckWX3VDqdKvuyEcv3dER3BHP0v7M4YigGfx1zGbV4lCNY293qPwiNbt +OaqaBdq2OUled6yMXvD9eTHmbtQZqyHitCBYMYhOXQA0Rb+InNz4YZeH0BiR9MSI +SwB/doJaWFCojuFkEL5TsLgvg6aoiscHo5xaIdefyeWxx/TkpJNHzMN69a+OyR9E +56hXTMAlMsuFlEdfIe1RlBgenPoaLYsaUh5z827gH2kOReawWtt/S8K2MMMsjdI2 +DqfkWKVz+56hnwtdlWonYF5mdxm1ChARNWfK7QjD62BhQojtiZgE +-----END CERTIFICATE----- diff --git a/webfront/rules.json b/webfront/rules.json index 36eaf9abcd..ca63732827 100644 --- a/webfront/rules.json +++ b/webfront/rules.json @@ -1,10 +1,9 @@ [ - {"Host": "local.com", "Path" : "/8001", "Forward": "localhost:8001"}, - {"Host": "local.com", "Path" : "/8002", "Forward": "localhost:8002"}, - {"Host": "local.com", "Path" : "/8003", "Forward": "localhost:8003"}, - {"Host": "local.com", "Path" : "/8004", "Forward": "localhost:8004"}, - {"Host": "local.com", "Path" : "/8005", "Forward": "localhost:8005"}, - {"Host": "local.com", "Path" : "/8006", "Forward": "localhost:8006"}, - {"Host": "local.com", "Path" : "/8007", "Forward": "localhost:8007"} + {"Host": "local.com", "Path" : "/login", "Forward": "localhost:8080", "secure": false}, + {"Host": "local.com", "Path" : "/8002", "Forward": "localhost:8002", "secure": true}, + {"Host": "local.com", "Path" : "/8003", "Forward": "localhost:8003", "secure": true}, + {"Host": "local.com", "Path" : "/8004", "Forward": "localhost:8004", "secure": true}, + {"Host": "local.com", "Path" : "/8005", "Forward": "localhost:8005", "secure": true}, + {"Host": "local.com", "Path" : "/8006", "Forward": "localhost:8006", "secure": true} ] diff --git a/webfront/webfront.go b/webfront/webfront.go index bc7faf8fc8..1b28c6ee66 100644 --- a/webfront/webfront.go +++ b/webfront/webfront.go @@ -11,7 +11,6 @@ import ( "flag" "fmt" jwt "github.com/dgrijalva/jwt-go" - "io/ioutil" "log" "net/http" "net/http/httputil" @@ -21,10 +20,6 @@ import ( "time" ) -type TokenResponse struct { - Token string -} - // Server implements an http.Handler that acts as a reverse proxy type Server struct { mu sync.RWMutex // guards the fields below @@ -37,6 +32,7 @@ type Rule struct { Host string // to match against request Host header Path string // to match against a path (start) Forward string // reverse proxy map-to + Secure bool // protect with jwt? handler http.Handler } @@ -99,57 +95,27 @@ func NewServer(file string, poll time.Duration) (*Server, error) { } // ServeHTTP matches the Request with a Rule and, if found, serves the -// request with the Rule's handler. +// request with the Rule's handler. If the rule's secure field is true, it will +// only allow access if the request has a valid JWT bearer token. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/login" { - username := "" - password := "" - body, err := ioutil.ReadAll(r.Body) - if err != nil { - log.Println("Error reading body: ", err.Error()) - http.Error(w, "Error reading body: "+err.Error(), http.StatusBadRequest) - return - } - var lj loginJson - log.Println(body) - err = json.Unmarshal(body, &lj) - if err != nil { - log.Println("Error unmarshalling JSON: ", err.Error()) - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - return - } - username = lj.User - password = lj.Password - - // TODO JvD - check username / password against Database here! - - token := jwt.New(jwt.SigningMethodHS256) - token.Claims["User"] = username - token.Claims["Password"] = password - token.Claims["exp"] = time.Now().Add(time.Hour * 24).Unix() - tokenString, err := token.SignedString([]byte("CAmeRAFiveSevenNineNine")) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - js, err := json.Marshal(TokenResponse{Token: tokenString}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(js) - return - } + + var isSecure = true token, err := validateToken(r.Header.Get("Authorization")) if err != nil { - log.Println("No valid token found!") - w.WriteHeader(http.StatusForbidden) - return + if s.isSecure(r) { + log.Println("No valid token found!") + w.WriteHeader(http.StatusForbidden) + return + } else { + isSecure = false + } } - // TODO JvD ^^ move into own function - log.Println("Token:", token.Claims["userid"]) + if isSecure { + log.Println(r.URL.Path, "identified user:", token.Claims["userid"]) + } else { + log.Println(r.URL.Path, "is not secured, giving access") + } if h := s.handler(r); h != nil { h.ServeHTTP(w, r) @@ -164,6 +130,26 @@ func rejectNoToken(handler http.Handler) http.HandlerFunc { } } +// isSecure returns the true if this path should be protected by a jwt +func (s *Server) isSecure(req *http.Request) bool { + s.mu.RLock() + defer s.mu.RUnlock() + h := req.Host + p := req.URL.Path + // Some clients include a port in the request host; strip it. + if i := strings.Index(h, ":"); i >= 0 { + h = h[:i] + } + for _, r := range s.rules { + // log.Println(p, "==", r.Path) + if strings.HasPrefix(p, r.Path) { + return r.Secure + } + } + log.Println("returning hard false") + return true +} + // handler returns the appropriate Handler for the given Request, // or nil if none found. func (s *Server) handler(req *http.Request) http.Handler { @@ -246,7 +232,7 @@ func makeHandler(r *Rule) http.Handler { Director: func(req *http.Request) { req.URL.Scheme = "https" req.URL.Host = h - req.URL.Path = "/boo1" // TODO JvD - regex to change path here + // req.URL.Path = "/boo1" // TODO JvD - regex to change path here }, } } From 632a67447e600366e909ee81bba13fb2cb4b2200 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sun, 10 Apr 2016 08:49:13 -0600 Subject: [PATCH 27/81] users name, like table --- {user => users}/.gitignore | 0 {user => users}/README.md | 0 {user => users}/main.go | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {user => users}/.gitignore (100%) rename {user => users}/README.md (100%) rename {user => users}/main.go (100%) diff --git a/user/.gitignore b/users/.gitignore similarity index 100% rename from user/.gitignore rename to users/.gitignore diff --git a/user/README.md b/users/README.md similarity index 100% rename from user/README.md rename to users/README.md diff --git a/user/main.go b/users/main.go similarity index 100% rename from user/main.go rename to users/main.go From 2a188b391aa5071d652d200b5776c4a41d3517c1 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sun, 10 Apr 2016 10:49:50 -0600 Subject: [PATCH 28/81] add testing --- apitest/testapi.go | 114 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 apitest/testapi.go diff --git a/apitest/testapi.go b/apitest/testapi.go new file mode 100644 index 0000000000..615c2c9c2a --- /dev/null +++ b/apitest/testapi.go @@ -0,0 +1,114 @@ +package main + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "io/ioutil" + "log" + "net/http" + // "strings" +) + +const LOGIN string = "/login" +const USERS string = "/users/" + +type TokenResponse struct { + Token string +} + +var tokenStr string +var urlStart string + +func login(client *http.Client) { + var jsonStr = []byte(`{"username":"jvd", "password": "secret"}`) + req, err := http.NewRequest("POST", urlStart+LOGIN, bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + var tokenResp TokenResponse + err = json.Unmarshal(body, &tokenResp) + if err != nil { + log.Println(err) + return + } + tokenStr = "Bearer " + tokenResp.Token +} + +func createUSer(client *http.Client) { + var jsonStr = []byte(`{"username":"jvdtest123", "password": "secret"}`) + req, err := http.NewRequest("POST", urlStart+USERS, bytes.NewBuffer(jsonStr)) + req.Header.Set("Authorization", tokenStr) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return + } + log.Println("Creating user", resp.StatusCode, resp.Status, string(body)) +} + +func editUSer(client *http.Client, userName string) { + var jsonStr = []byte(`{"password": "secret1212changed"}`) + req, err := http.NewRequest("PUT", urlStart+USERS+userName, bytes.NewBuffer(jsonStr)) + req.Header.Set("Authorization", tokenStr) + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return + } + log.Println("Editing user", resp.StatusCode, resp.Status, string(body)) +} + +func deleteUSer(client *http.Client, userName string) { + req, err := http.NewRequest("DELETE", urlStart+USERS+userName, nil) + req.Header.Set("Authorization", tokenStr) + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return + } + log.Println("Deleting user", resp.StatusCode, resp.Status, string(body)) +} + +func main() { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + urlStart = "https://localhost:9000" + login(client) + log.Println("Token:" + tokenStr) + log.Print("EXPECT 200 OK") + createUSer(client) + log.Print("EXPECT ERROR:") + createUSer(client) + log.Print("EXPECT 200 OK") + editUSer(client, "jvdtest123") + log.Print("EXPECT 0 ROWS:") + editUSer(client, "notthere") + log.Print("EXPECT 200 OK") + deleteUSer(client, "jvdtest123") +} From 5173276df51c7588a3dff1bcf36783ae52014a6b Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sun, 10 Apr 2016 10:50:57 -0600 Subject: [PATCH 29/81] mv main to other name --- auth/{main.go => auth.go} | 0 users/{main.go => users.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename auth/{main.go => auth.go} (100%) rename users/{main.go => users.go} (100%) diff --git a/auth/main.go b/auth/auth.go similarity index 100% rename from auth/main.go rename to auth/auth.go diff --git a/users/main.go b/users/users.go similarity index 100% rename from users/main.go rename to users/users.go From a5a704614d3160f4c4c1283b6055d515ace72fbe Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sun, 10 Apr 2016 12:02:38 -0600 Subject: [PATCH 30/81] check point, remove cert stuff --- auth/.gitignore | 2 ++ auth/auth.go | 70 ++++++++++++++++---------------------------- auth/server.key | 27 ----------------- auth/server.pem | 21 ------------- users/.gitignore | 2 ++ users/users.go | 14 ++++----- webfront/rules.json | 2 +- webfront/webfront.go | 4 +-- 8 files changed, 39 insertions(+), 103 deletions(-) delete mode 100644 auth/server.key delete mode 100644 auth/server.pem diff --git a/auth/.gitignore b/auth/.gitignore index 143c64680f..cb78992371 100644 --- a/auth/.gitignore +++ b/auth/.gitignore @@ -1 +1,3 @@ *.config +server.pem +server.key diff --git a/auth/auth.go b/auth/auth.go index 5bc748c6cd..56db78585f 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -13,7 +13,7 @@ import ( "net/http" "os" "path" - "strings" + // "strings" "time" ) @@ -87,30 +87,27 @@ func main() { http.HandleFunc("/", handler) // http.ListenAndServe(":8080", nil) - http.ListenAndServeTLS(":8080", "server.pem", "server.key", nil) - - if err != nil { - log.Println(err) - } + log.Fatal(http.ListenAndServeTLS(":"+config.ListenerPort, "server.pem", "server.key", nil)) } -func validateToken(tokenString string) (*jwt.Token, error) { - - tokenString = strings.Replace(tokenString, "Bearer ", "", 1) - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { - // Don't forget to validate the alg is what you expect: - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) - } - return []byte("CAmeRAFiveSevenNineNine"), nil - }) - if err == nil && token.Valid { - log.Println("TOKEN IS GOOD -- user:", token.Claims["userid"], " role:", token.Claims["role"]) - } else { - log.Println("TOKEN IS BAD", err) - } - return token, err -} +// func validateToken(tokenString string) (*jwt.Token, error) { + +// tokenString = strings.Replace(tokenString, "Bearer ", "", 1) +// token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { +// // Don't forget to validate the alg is what you expect: +// if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { +// return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) +// } +// return []byte("CAmeRAFiveSevenNineNine"), nil +// }) + +// if err == nil && token.Valid { +// log.Println("TOKEN IS GOOD -- user:", token.Claims["userid"], " role:", token.Claims["role"]) +// } else { +// log.Println("TOKEN IS BAD", err) +// } +// return token, err +// } func InitializeDatabase(username, password, dbname, server string, port uint) (*sqlx.DB, error) { connString := fmt.Sprintf("host=%s dbname=%s user=%s password=%s sslmode=disable", server, dbname, username, password) @@ -133,24 +130,23 @@ func handler(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { var u User userlist := []User{} - username := "" - password := "" + // username := "" + // password := "" body, err := ioutil.ReadAll(r.Body) + log.Println(string(body)) if err != nil { log.Println("Error reading body: ", err.Error()) http.Error(w, "Error reading body: "+err.Error(), http.StatusBadRequest) return } - // var lj loginJson - // log.Println(body) err = json.Unmarshal(body, &u) if err != nil { log.Println("Error unmarshalling JSON: ", err.Error()) http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) return } - // username = lj.User - // password = lj.Password + // username = u.Username + // password = u.Password stmt, err := db.PrepareNamed("SELECT * FROM users WHERE username=:username") err = stmt.Select(&userlist, u) @@ -165,8 +161,7 @@ func handler(w http.ResponseWriter, r *http.Request) { } token := jwt.New(jwt.SigningMethodHS256) - token.Claims["User"] = username - token.Claims["Password"] = password + token.Claims["User"] = u.Username token.Claims["exp"] = time.Now().Add(time.Hour * 24).Unix() tokenString, err := token.SignedString([]byte("CAmeRAFiveSevenNineNine")) if err != nil { @@ -180,19 +175,6 @@ func handler(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") w.Write(js) - // var u User - // userlist := []User{} - // body, err := ioutil.ReadAll(r.Body) - // if err != nil { - // log.Println(err) - // } - // err = json.Unmarshal(body, &u) - // if err != nil { - // log.Println(err) - // retErr(w, http.StatusInternalServerError) - // return - // } - } retErr(w, http.StatusNotFound) } diff --git a/auth/server.key b/auth/server.key deleted file mode 100644 index 2729786cc5..0000000000 --- a/auth/server.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEArgqw5G7wuuw50ly1lM9UIqzQAVX68LIcjTWDw0CpYpeaPB2P -opU0KBB1uh18kJbMl+UvNwxEtbfsk2cCnFkVhf/S26AJ6mGkfofw2pcfsUrHbicr -NK693N/EiWgBAfb9xeD9FWcDWZJ8UcPdB4O5O10Xewv0f3Dc9UMz3f9833TOvoPn -4cbkj/MDiebfTjqMZoRhUE3pKs4tf9wEC5m/QKKMDWVw/7oSXrgXxKwDemD10LBc -o0U6uANuQVMaqfJfn3TbqgfPBSc+Tjxs+L248dtL3GQ+06/lxZHOcFYQG3V2k/O1 -r9q9xBGDLJ0C4gkh0BJWMgOIErjM+vbvHj4EuwIDAQABAoIBAQCCo2G+RgwKsxB/ -97kQ90NXN9J2fjx7eaZ21EmQQTQj3loWb7YGOurIe/pydnmu6pKuHYkAUAQvRtyc -lEDgbPn7+FglJQ7sUNixZYcD/VX7tX7XDy2eQOAhMBINdfTzrZs1cjlmXEmmbY9F -rLh3UIe0qJ7cbM/c7THeI9CF/aWU8JK5EnlsQjHTsqXFh3bB4jZJMJbERALh/wb0 -aaYhzRUbuaVh4pwM02WjX/3Zd+D6mkiT3Jv6Vw7V/f1Gv5NIaAZVEvJdXdAZlXvo -qXSbuXZ7CxJnGOQZmLTnH8TThaT6CWnBjD2fIUL89T72nKoqwgvd9+kwbFR6UkIH -8rRE1brpAoGBANsTkskiey/nMLRsQNbOw2zlqQTxgGnOPF0CMTrrOjgVsDfvO/p3 -53hZtmL6YcdQqphXzupu+bDOa0cIwd4lCck8Q4fauq5CpYcEX6rohSa3qASBZrsI -Q/7O9UOcKb2y+7Q8fqSqP20d5pL7zDHXcb9wSuR6KPLr8WBFwNVQQvp9AoGBAMtg -BNs0vc2DmEd3C/ZZi0L6FE7vMvV/VS2BN+OaHpPTKrvt88S/316spmansfwUFqIC -DT0RCwTJ9HnQnPdOYSeoCkDm69jpUft9xTAR0s0E+JNL4a7O62m8Psbq9Cm8QK7O -WQQeiNBxQJer6FxO9IBSsoij2NRTFlC5lDAXK2mXAoGBALPyHOSXLQZI6MmaDZwj -k4P0FGPaFndSDmT7MwLJSjXdIrSfDe4K5RcG4XflX8E+shGWNoB7jR68PoPHXq6g -T6x6kqGZeTM/zQTdA9O3T/9gZWLmA0LnZCp9Rho1wFdwF62Q7xlEOfrfz4+1waDC -7Uyo6OtIqC/4u3gvc1gDqDBlAoGAFhhIbsg8FqfFU35LfmgcCRrCOhEiqCk9R8RS -HhgQWl8GtYQUh7uQuOho654saAaGrvMqHQhM+ig2t0VCyFtrkrNe6c4ssVQn4/q2 -AZLuI/f2SYpk6mjwinw5FcNQ43fgTx177kai9oJXUiMLC3xIc+iIHPAwDmE4e2yt -/MuHm+ECgYEAybHU3EjvHG9aCxhyYc4a3odES4Tz/R48N8OHejTxPhgUjilofU8h -r+UQTFrHCh0WQnVtadfex0x5/jYGU+jae6W0Tdvj4oa8imSMOil+biXaYcFFIWXr -JhPaaDt0V8GwpmeHCBj/6QOIIUDK83XqYwv7aDrqBMOFFl2QumrckQM= ------END RSA PRIVATE KEY----- diff --git a/auth/server.pem b/auth/server.pem deleted file mode 100644 index 2f47ad8bd1..0000000000 --- a/auth/server.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDgzCCAmugAwIBAgIJAP9mv1xFYeiyMA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV -BAYTAlVTMQswCQYDVQQIDAJDTzEPMA0GA1UEBwwGRGVudmVyMQswCQYDVQQKDAJD -VTEeMBwGCSqGSIb3DQEJARYPanZkQGtudXRzZWwuY29tMB4XDTE2MDMwNTIwMjYy -NFoXDTI2MDMwMzIwMjYyNFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMQ8w -DQYDVQQHDAZEZW52ZXIxCzAJBgNVBAoMAkNVMR4wHAYJKoZIhvcNAQkBFg9qdmRA -a251dHNlbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCuCrDk -bvC67DnSXLWUz1QirNABVfrwshyNNYPDQKlil5o8HY+ilTQoEHW6HXyQlsyX5S83 -DES1t+yTZwKcWRWF/9LboAnqYaR+h/Dalx+xSsduJys0rr3c38SJaAEB9v3F4P0V -ZwNZknxRw90Hg7k7XRd7C/R/cNz1QzPd/3zfdM6+g+fhxuSP8wOJ5t9OOoxmhGFQ -Tekqzi1/3AQLmb9AoowNZXD/uhJeuBfErAN6YPXQsFyjRTq4A25BUxqp8l+fdNuq -B88FJz5OPGz4vbjx20vcZD7Tr+XFkc5wVhAbdXaT87Wv2r3EEYMsnQLiCSHQElYy -A4gSuMz69u8ePgS7AgMBAAGjUDBOMB0GA1UdDgQWBBRbyiGJ5h0Npd277VCQ6ePS -N8s9fDAfBgNVHSMEGDAWgBRbyiGJ5h0Npd277VCQ6ePSN8s9fDAMBgNVHRMEBTAD -AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBSUalyrKENU24QxW9h3t3PbaxOQng8Gs8s -UIPJQh1Z2ckWX3VDqdKvuyEcv3dER3BHP0v7M4YigGfx1zGbV4lCNY293qPwiNbt -OaqaBdq2OUled6yMXvD9eTHmbtQZqyHitCBYMYhOXQA0Rb+InNz4YZeH0BiR9MSI -SwB/doJaWFCojuFkEL5TsLgvg6aoiscHo5xaIdefyeWxx/TkpJNHzMN69a+OyR9E -56hXTMAlMsuFlEdfIe1RlBgenPoaLYsaUh5z827gH2kOReawWtt/S8K2MMMsjdI2 -DqfkWKVz+56hnwtdlWonYF5mdxm1ChARNWfK7QjD62BhQojtiZgE ------END CERTIFICATE----- diff --git a/users/.gitignore b/users/.gitignore index 143c64680f..cb78992371 100644 --- a/users/.gitignore +++ b/users/.gitignore @@ -1 +1,3 @@ *.config +server.pem +server.key diff --git a/users/users.go b/users/users.go index 174fe0a7f6..57dd0fe1b1 100644 --- a/users/users.go +++ b/users/users.go @@ -80,11 +80,8 @@ func main() { Logger.Printf("Starting server on port " + config.ListenerPort + "...") http.HandleFunc("/", handler) - http.ListenAndServe(":8080", nil) + log.Fatal(http.ListenAndServeTLS(":"+config.ListenerPort, "server.pem", "server.key", nil)) - if err != nil { - log.Println(err) - } } func InitializeDatabase(username, password, dbname, server string, port uint) (*sqlx.DB, error) { @@ -106,7 +103,7 @@ func handler(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.URL.Scheme, r.Host, r.URL.RequestURI()) if r.Method == "GET" { - if r.URL.Path == "/" { + if r.URL.Path == "/users" { userlist := []User{} err := db.Select(&userlist, "SELECT * FROM users") if err != nil { @@ -116,7 +113,7 @@ func handler(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) enc.Encode(userlist) } else { - username := strings.Replace(r.URL.Path, "/", "", 1) + username := strings.Replace(r.URL.Path, "/users/", "", 1) userlist := []User{} argument := User{} argument.Username = username @@ -169,9 +166,10 @@ func handler(w http.ResponseWriter, r *http.Request) { retErr(w, http.StatusInternalServerError) return } - u.Username = strings.Replace(r.URL.Path, "/", "", 1) // overwrite the username in the json, the path gets checked. + u.Username = strings.Replace(r.URL.Path, "/users/", "", 1) // overwrite the username in the json, the path gets checked. // TODO encrypt passwd before storing. sqlString := "UPDATE users SET last_name=:last_name, first_name=:first_name, password=:password WHERE username=:username" + log.Println(sqlString) result, err := db.NamedExec(sqlString, u) if err != nil { log.Println(err) @@ -182,7 +180,7 @@ func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Done! (%s Rows Affected)", rows) } else if r.Method == "DELETE" { argument := User{} - argument.Username = strings.Replace(r.URL.Path, "/", "", 1) + argument.Username = strings.Replace(r.URL.Path, "/users/", "", 1) result, err := db.NamedExec("DELETE FROM users WHERE username=:username", argument) if err != nil { log.Println(err) diff --git a/webfront/rules.json b/webfront/rules.json index ca63732827..836f22071e 100644 --- a/webfront/rules.json +++ b/webfront/rules.json @@ -1,6 +1,6 @@ [ {"Host": "local.com", "Path" : "/login", "Forward": "localhost:8080", "secure": false}, - {"Host": "local.com", "Path" : "/8002", "Forward": "localhost:8002", "secure": true}, + {"Host": "local.com", "Path" : "/users", "Forward": "localhost:8081", "secure": true}, {"Host": "local.com", "Path" : "/8003", "Forward": "localhost:8003", "secure": true}, {"Host": "local.com", "Path" : "/8004", "Forward": "localhost:8004", "secure": true}, {"Host": "local.com", "Path" : "/8005", "Forward": "localhost:8005", "secure": true}, diff --git a/webfront/webfront.go b/webfront/webfront.go index 1b28c6ee66..9c498a5c79 100644 --- a/webfront/webfront.go +++ b/webfront/webfront.go @@ -76,9 +76,9 @@ func validateToken(tokenString string) (*jwt.Token, error) { }) if err == nil && token.Valid { - log.Println("TOKEN IS GOOD -- user:", token.Claims["userid"], " role:", token.Claims["role"]) + log.Println("Token is good -- user:", token.Claims["User"], token.Claims) } else { - log.Println("TOKEN IS BAD", err) + log.Println("Token is bad", err) } return token, err } From 3cb87b931d673399265264c1f7467ebb9e75d212 Mon Sep 17 00:00:00 2001 From: Jan van Doorn Date: Sun, 10 Apr 2016 13:06:07 -0600 Subject: [PATCH 31/81] Logger and more cleanup --- apitest/testapi.go | 1 - auth/auth.go | 84 ++++++++++++++++---------------------------- users/users.go | 57 ++++++++++++++++-------------- webfront/webfront.go | 30 +++++++--------- 4 files changed, 74 insertions(+), 98 deletions(-) diff --git a/apitest/testapi.go b/apitest/testapi.go index 615c2c9c2a..6b8b01cd4c 100644 --- a/apitest/testapi.go +++ b/apitest/testapi.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "log" "net/http" - // "strings" ) const LOGIN string = "/login" diff --git a/auth/auth.go b/auth/auth.go index 56db78585f..b78387e22c 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -1,19 +1,16 @@ package main import ( - // "encoding/gob" "encoding/json" "fmt" + jwt "github.com/dgrijalva/jwt-go" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" - // null "gopkg.in/guregu/null.v3" - jwt "github.com/dgrijalva/jwt-go" "io/ioutil" "log" "net/http" "os" "path" - // "strings" "time" ) @@ -49,66 +46,53 @@ func printUsage() { "dbPort":5432, "listenerPort":"8080" }` - log.Println("Usage: " + path.Base(os.Args[0]) + " configfile") - log.Println("") - log.Println("Example config file:") - log.Println(exampleConfig) + Logger.Println("Usage: " + path.Base(os.Args[0]) + " configfile") + Logger.Println("") + Logger.Println("Example config file:") + Logger.Println(exampleConfig) } +var Logger *log.Logger + func main() { if len(os.Args) < 2 { printUsage() return } - log.SetOutput(os.Stdout) + // log.SetOutput(os.Stdout) + Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile) file, err := os.Open(os.Args[1]) if err != nil { - log.Println("Error opening config file:", err) + Logger.Println("Error opening config file:", err) return } decoder := json.NewDecoder(file) config := Config{} err = decoder.Decode(&config) if err != nil { - log.Println("Error reading config file:", err) + Logger.Println("Error reading config file:", err) return } db, err = InitializeDatabase(config.DbUser, config.DbPassword, config.DbName, config.DbServer, config.DbPort) if err != nil { - log.Println("Error initializing database:", err) + Logger.Println("Error initializing database:", err) return } - var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile) - Logger.Printf("Starting server on port " + config.ListenerPort + "...") - http.HandleFunc("/", handler) - // http.ListenAndServe(":8080", nil) - log.Fatal(http.ListenAndServeTLS(":"+config.ListenerPort, "server.pem", "server.key", nil)) + if _, err := os.Stat("server.pem"); os.IsNotExist(err) { + Logger.Fatal("server.pem file not found") + } + if _, err := os.Stat("server.key"); os.IsNotExist(err) { + Logger.Fatal("server.key file not found") + } + Logger.Printf("Starting server on port " + config.ListenerPort + "...") + Logger.Fatal(http.ListenAndServeTLS(":"+config.ListenerPort, "server.pem", "server.key", nil)) } -// func validateToken(tokenString string) (*jwt.Token, error) { - -// tokenString = strings.Replace(tokenString, "Bearer ", "", 1) -// token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { -// // Don't forget to validate the alg is what you expect: -// if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { -// return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) -// } -// return []byte("CAmeRAFiveSevenNineNine"), nil -// }) - -// if err == nil && token.Valid { -// log.Println("TOKEN IS GOOD -- user:", token.Claims["userid"], " role:", token.Claims["role"]) -// } else { -// log.Println("TOKEN IS BAD", err) -// } -// return token, err -// } - func InitializeDatabase(username, password, dbname, server string, port uint) (*sqlx.DB, error) { connString := fmt.Sprintf("host=%s dbname=%s user=%s password=%s sslmode=disable", server, dbname, username, password) @@ -120,43 +104,33 @@ func InitializeDatabase(username, password, dbname, server string, port uint) (* return db, nil } -func retErr(w http.ResponseWriter, status int) { - w.WriteHeader(status) -} - func handler(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.URL.Scheme, r.Host, r.URL.RequestURI()) + Logger.Println(r.Method, r.URL.Scheme, r.Host, r.URL.RequestURI()) if r.Method == "POST" { var u User userlist := []User{} - // username := "" - // password := "" body, err := ioutil.ReadAll(r.Body) - log.Println(string(body)) if err != nil { - log.Println("Error reading body: ", err.Error()) + Logger.Println("Error reading body: ", err.Error()) http.Error(w, "Error reading body: "+err.Error(), http.StatusBadRequest) return } err = json.Unmarshal(body, &u) if err != nil { - log.Println("Error unmarshalling JSON: ", err.Error()) + Logger.Println("Error unmarshalling JSON: ", err.Error()) http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) return } - // username = u.Username - // password = u.Password - stmt, err := db.PrepareNamed("SELECT * FROM users WHERE username=:username") err = stmt.Select(&userlist, u) if err != nil { - log.Println(err) - retErr(w, http.StatusInternalServerError) + Logger.Println(err.Error()) + http.Error(w, "Database error: "+err.Error(), http.StatusInternalServerError) return } if len(userlist) == 0 || userlist[0].Password != u.Password { - retErr(w, http.StatusUnauthorized) + http.Error(w, "Invalid username/password ", http.StatusUnauthorized) return } @@ -165,16 +139,20 @@ func handler(w http.ResponseWriter, r *http.Request) { token.Claims["exp"] = time.Now().Add(time.Hour * 24).Unix() tokenString, err := token.SignedString([]byte("CAmeRAFiveSevenNineNine")) if err != nil { + Logger.Println(err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } js, err := json.Marshal(TokenResponse{Token: tokenString}) if err != nil { + Logger.Println(err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } + // w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") w.Write(js) + return } - retErr(w, http.StatusNotFound) + http.Error(w, r.Method+" "+r.URL.Path+" not valid for this microservice", http.StatusNotFound) } diff --git a/users/users.go b/users/users.go index 57dd0fe1b1..9f5b80fc2d 100644 --- a/users/users.go +++ b/users/users.go @@ -1,12 +1,10 @@ package main import ( - // "encoding/gob" "encoding/json" "fmt" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" - // null "gopkg.in/guregu/null.v3" "io/ioutil" "log" "net/http" @@ -33,6 +31,7 @@ type User struct { } var db *sqlx.DB // global and simple +var Logger *log.Logger func printUsage() { exampleConfig := `{ @@ -43,10 +42,10 @@ func printUsage() { "dbPort":5432, "listenerPort":"8080" }` - log.Println("Usage: " + path.Base(os.Args[0]) + " configfile") - log.Println("") - log.Println("Example config file:") - log.Println(exampleConfig) + Logger.Println("Usage: " + path.Base(os.Args[0]) + " configfile") + Logger.Println("") + Logger.Println("Example config file:") + Logger.Println(exampleConfig) } func main() { @@ -55,33 +54,36 @@ func main() { return } - log.SetOutput(os.Stdout) + Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile) file, err := os.Open(os.Args[1]) if err != nil { - log.Println("Error opening config file:", err) + Logger.Println("Error opening config file:", err) return } decoder := json.NewDecoder(file) config := Config{} err = decoder.Decode(&config) if err != nil { - log.Println("Error reading config file:", err) + Logger.Println("Error reading config file:", err) return } db, err = InitializeDatabase(config.DbUser, config.DbPassword, config.DbName, config.DbServer, config.DbPort) if err != nil { - log.Println("Error initializing database:", err) + Logger.Println("Error initializing database:", err) return } - var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile) - Logger.Printf("Starting server on port " + config.ListenerPort + "...") - http.HandleFunc("/", handler) - log.Fatal(http.ListenAndServeTLS(":"+config.ListenerPort, "server.pem", "server.key", nil)) - + if _, err := os.Stat("server.pem"); os.IsNotExist(err) { + Logger.Fatal("server.pem file not found") + } + if _, err := os.Stat("server.key"); os.IsNotExist(err) { + Logger.Fatal("server.key file not found") + } + Logger.Printf("Starting server on port " + config.ListenerPort + "...") + Logger.Fatal(http.ListenAndServeTLS(":"+config.ListenerPort, "server.pem", "server.key", nil)) } func InitializeDatabase(username, password, dbname, server string, port uint) (*sqlx.DB, error) { @@ -101,13 +103,13 @@ func retErr(w http.ResponseWriter, status int) { func handler(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.URL.Scheme, r.Host, r.URL.RequestURI()) + Logger.Println(r.Method, r.URL.Scheme, r.Host, r.URL.RequestURI()) if r.Method == "GET" { if r.URL.Path == "/users" { userlist := []User{} err := db.Select(&userlist, "SELECT * FROM users") if err != nil { - log.Println(err) + Logger.Println(err) } w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) @@ -120,7 +122,7 @@ func handler(w http.ResponseWriter, r *http.Request) { stmt, err := db.PrepareNamed("SELECT * FROM users WHERE username=:username") err = stmt.Select(&userlist, argument) if err != nil { - log.Println(err) + Logger.Println(err) retErr(w, http.StatusInternalServerError) return } @@ -136,11 +138,11 @@ func handler(w http.ResponseWriter, r *http.Request) { var u User body, err := ioutil.ReadAll(r.Body) if err != nil { - log.Println(err) + Logger.Println(err) } err = json.Unmarshal(body, &u) if err != nil { - log.Println(err) + Logger.Println(err) retErr(w, http.StatusInternalServerError) return } @@ -148,7 +150,7 @@ func handler(w http.ResponseWriter, r *http.Request) { sqlString := "INSERT INTO users (username, last_name, first_name, password) VALUES (:username, :last_name, :first_name, :password)" result, err := db.NamedExec(sqlString, u) if err != nil { - log.Println(err) + Logger.Println(err) retErr(w, http.StatusInternalServerError) return } @@ -158,21 +160,21 @@ func handler(w http.ResponseWriter, r *http.Request) { var u User body, err := ioutil.ReadAll(r.Body) if err != nil { - log.Println(err) + Logger.Println(err) } err = json.Unmarshal(body, &u) if err != nil { - log.Println(err) + Logger.Println(err) retErr(w, http.StatusInternalServerError) return } u.Username = strings.Replace(r.URL.Path, "/users/", "", 1) // overwrite the username in the json, the path gets checked. // TODO encrypt passwd before storing. sqlString := "UPDATE users SET last_name=:last_name, first_name=:first_name, password=:password WHERE username=:username" - log.Println(sqlString) + Logger.Println(sqlString) result, err := db.NamedExec(sqlString, u) if err != nil { - log.Println(err) + Logger.Println(err) retErr(w, http.StatusInternalServerError) return } @@ -183,12 +185,13 @@ func handler(w http.ResponseWriter, r *http.Request) { argument.Username = strings.Replace(r.URL.Path, "/users/", "", 1) result, err := db.NamedExec("DELETE FROM users WHERE username=:username", argument) if err != nil { - log.Println(err) + Logger.Println(err) retErr(w, http.StatusInternalServerError) return } rows, _ := result.RowsAffected() fmt.Fprintf(w, "Done! (%s Rows Affected)", rows) + } else { + http.Error(w, r.Method+" "+r.URL.Path+" not valid for this microservice", http.StatusNotFound) } - retErr(w, http.StatusNotFound) } diff --git a/webfront/webfront.go b/webfront/webfront.go index 9c498a5c79..2162643eca 100644 --- a/webfront/webfront.go +++ b/webfront/webfront.go @@ -4,9 +4,7 @@ package main import ( - // "crypto/sha1" "crypto/tls" - // "encoding/hex" "encoding/json" "flag" "fmt" @@ -43,9 +41,7 @@ type loginJson struct { } var ( - httpsAddr = flag.String("https", "", "HTTPS listen address (leave empty to disable)") - certFile = flag.String("https_cert", "", "HTTPS certificate file") - keyFile = flag.String("https_key", "", "HTTPS key file") + httpsAddr = flag.String("https", "", "HTTPS listen address") ruleFile = flag.String("rules", "", "rule definition file") pollInterval = flag.Duration("poll", time.Second*10, "file poll interval") ) @@ -61,25 +57,24 @@ func main() { // override the default so we can use self-signed certs on our microservices // and use a self-signed cert in this server http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - http.ListenAndServeTLS(*httpsAddr, *certFile, *keyFile, s) + if _, err := os.Stat("server.pem"); os.IsNotExist(err) { + log.Fatal("server.pem file not found") + } + if _, err := os.Stat("server.key"); os.IsNotExist(err) { + log.Fatal("server.key file not found") + } + http.ListenAndServeTLS(*httpsAddr, "server.pem", "server.key", s) } func validateToken(tokenString string) (*jwt.Token, error) { tokenString = strings.Replace(tokenString, "Bearer ", "", 1) token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { - // Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return []byte("CAmeRAFiveSevenNineNine"), nil }) - - if err == nil && token.Valid { - log.Println("Token is good -- user:", token.Claims["User"], token.Claims) - } else { - log.Println("Token is bad", err) - } return token, err } @@ -98,12 +93,12 @@ func NewServer(file string, poll time.Duration) (*Server, error) { // request with the Rule's handler. If the rule's secure field is true, it will // only allow access if the request has a valid JWT bearer token. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - + // assume the token is required, most used code path, only check to see if path is open when fails. var isSecure = true token, err := validateToken(r.Header.Get("Authorization")) if err != nil { if s.isSecure(r) { - log.Println("No valid token found!") + log.Println(r.URL.Path + ": valid token required, but none found!") w.WriteHeader(http.StatusForbidden) return } else { @@ -112,15 +107,16 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if isSecure { - log.Println(r.URL.Path, "identified user:", token.Claims["userid"]) + log.Println(r.Method+" "+r.URL.Path+": valid token found, identified user:", token.Claims["User"]) } else { - log.Println(r.URL.Path, "is not secured, giving access") + log.Println(r.Method + " " + r.URL.Path + ": no token required") } if h := s.handler(r); h != nil { h.ServeHTTP(w, r) return } + log.Println(r.Method + " " + r.URL.Path + ": no mapping in rules file!") http.Error(w, "Not found.", http.StatusNotFound) } From 7b81630daf0d922fb7a9d6f2df5be3dfead67348 Mon Sep 17 00:00:00 2001 From: Michael Albers Date: Sun, 10 Apr 2016 17:13:13 -0600 Subject: [PATCH 32/81] Create RetrieveVideo service. Small updates to CameraFeed. --- CameraFeed/CameraFeed.js | 54 +++----- CameraFeed/CameraFeed_v1.js | 5 +- CameraFeed/Dockerfile | 2 +- CameraFeed/README.txt | 23 ++++ CameraFeed/keys/cert.pem | 22 --- CameraFeed/keys/key.pem | 28 ---- CameraFeed/src/JPEGMongoDBWriter.cpp | 22 ++- CameraFeed/src/main.cpp | 2 - RetrieveVideo/.gitignore | 2 + RetrieveVideo/Dockerfile | 19 +++ RetrieveVideo/README.txt | 23 ++++ RetrieveVideo/RetrieveVideo.js | 197 +++++++++++++++++++++++++++ RetrieveVideo/package.json | 18 +++ 13 files changed, 311 insertions(+), 106 deletions(-) create mode 100644 CameraFeed/README.txt delete mode 100644 CameraFeed/keys/cert.pem delete mode 100755 CameraFeed/keys/key.pem create mode 100644 RetrieveVideo/.gitignore create mode 100644 RetrieveVideo/Dockerfile create mode 100644 RetrieveVideo/README.txt create mode 100644 RetrieveVideo/RetrieveVideo.js create mode 100644 RetrieveVideo/package.json diff --git a/CameraFeed/CameraFeed.js b/CameraFeed/CameraFeed.js index 0637622873..bf1cc109fa 100644 --- a/CameraFeed/CameraFeed.js +++ b/CameraFeed/CameraFeed.js @@ -5,15 +5,13 @@ "use strict"; -var https = require("https"); +var http = require("http"); var url = require("url"); const fs = require('fs'); var CameraFeedError = require('./CameraFeedError.js'); -// TODO: remember to allow security exception until better certs are had - // Model URI: -// https://localhost:8080/CameraFeed/v1? +// https://localhost:8080/v1? /** Debug flag for development*/ var debugFlag = true; @@ -21,9 +19,6 @@ var debugFlag = true; /** HTTP server listen port */ const PORT = 8080; -/** REST resources */ -const CAMERA_RESOURCE = "CameraFeed" - /** Name of the server for HTTP header */ const SERVER = "CameraFeed/0.1" @@ -36,42 +31,30 @@ var apiMap = { }; /* - * Given the pathname of a URI (i.e., /CameraFeed/v1) verifies that - * the resource is valid and returns the API version. + * Given the pathname of a URI (i.e., /v1) returns the API version. * * @param path URI pathname * @return API version string - * @throws CameraFeedError on invalid pathname or resource + * @throws CameraFeedError on invalid pathname */ -var verifyResourceVersion = function(path) { +var getVersion = function(path) { var paths = path.split("/"); - if (paths.length != 3) + if (paths.length != 2) { var returnJSON = CameraFeedError.buildJSON( CameraFeedError.InvalidPath, - 'Resource path did not contain correct number of parts. Must be /' + - CAMERA_RESOURCE + '/'); - throw new CameraFeedError.CameraFeedError( - "Invalid Resource", 400, returnJSON); - } - - var resource = paths[1]; - var apiVersion = paths[2]; - - if (CAMERA_RESOURCE.localeCompare(resource) != 0) - { - var returnJSON = CameraFeedError.buildJSON( - CameraFeedError.InvalidPath, - 'Invalid resource. Must be ' + CAMERA_RESOURCE); + 'Resource path did not contain correct number of parts. ' + + 'Must be /'); throw new CameraFeedError.CameraFeedError( "Invalid Resource", 400, returnJSON); } + var apiVersion = paths[1]; return apiVersion; } /** - * Request event handler for HttpsServer object. + * Request event handler for HttpServer object. * See https://nodejs.org/api/http.html#http_event_request */ var requestHandler = function(request, response) @@ -90,7 +73,7 @@ var requestHandler = function(request, response) try { - var apiVersion = verifyResourceVersion(parsedURL.pathname); + var apiVersion = getVersion(parsedURL.pathname); if (debugFlag) { console.log("--Client requested API version: " + apiVersion); @@ -116,8 +99,12 @@ var requestHandler = function(request, response) // This helps in development when some other exception besides CameraFeedError // might be getting thrown. if (debugFlag) { - console.log(e.name); + console.log("------------------"); + console.log("Debug output for exception thrown during request"); + console.log("Name: " + e.name); + console.log("Stack:"); console.log(e.stack); + console.log("------------------"); } response.writeHead(e.getHttpCode(), { @@ -134,11 +121,4 @@ if (debugFlag) { console.log("Starting CameraFeed microservice..."); } -const options = { - key: fs.readFileSync('keys/key.pem'), - cert: fs.readFileSync('keys/cert.pem') -}; - -https.createServer(options, requestHandler).listen(PORT); - -// TODO: next need to register with API gateway +http.createServer(requestHandler).listen(PORT); diff --git a/CameraFeed/CameraFeed_v1.js b/CameraFeed/CameraFeed_v1.js index c5352d1106..2c2dc5b940 100644 --- a/CameraFeed/CameraFeed_v1.js +++ b/CameraFeed/CameraFeed_v1.js @@ -5,8 +5,7 @@ "use strict"; -// Model URI: -// https://localhost:8080/CameraFeed/v1?action=[start|stop]&camera_id=[id] +// API defined in REAME.txt var path = require('path'); var CameraFeedError = require('./CameraFeedError.js'); @@ -103,7 +102,7 @@ function executeStart(cameraId) { // TODO: // - check if camera already streaming // if so, success - // if not, spawn child process to retrieve feed (C++, libcurl) + // if not, spawn child process to retrieve feed // update DB to say feed is started // (need to errors from child process) } diff --git a/CameraFeed/Dockerfile b/CameraFeed/Dockerfile index 86b05e63fd..70048f65e5 100644 --- a/CameraFeed/Dockerfile +++ b/CameraFeed/Dockerfile @@ -43,7 +43,7 @@ RUN make && make install # Custom Code ############################## -# Brind in CameraFeed source +# Bring in CameraFeed source RUN mkdir /usr/src/CameraFeed WORKDIR /usr/src/CameraFeed # Manual copy, excludes Dockerfile, emacs tmp files diff --git a/CameraFeed/README.txt b/CameraFeed/README.txt new file mode 100644 index 0000000000..d617e215de --- /dev/null +++ b/CameraFeed/README.txt @@ -0,0 +1,23 @@ +#################################### + README For CameraFeed Microservice +#################################### + +Description +=========== +This microservice handles reading the live video stream from the camera. + +API +=== +/?[args...] + +API Versions +------------ ++V1 + /v1?action=[start|stop]&camera_id=[id] + + action - whether to start or stop recording. + camera_id - ID string of the camera from which to start or stop recording + + Example: + /v1/action=start&camera_id=12345 + diff --git a/CameraFeed/keys/cert.pem b/CameraFeed/keys/cert.pem deleted file mode 100644 index b0aaf22b5f..0000000000 --- a/CameraFeed/keys/cert.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDrTCCApWgAwIBAgIJAOKKLis9KpfsMA0GCSqGSIb3DQEBCwUAMG0xCzAJBgNV -BAYTAlVTMREwDwYDVQQIDAhDb2xvcmFkbzEPMA0GA1UEBwwGRGVudmVyMQwwCgYD -VQQKDANNUkoxFzAVBgNVBAMMDk1pY2hhZWwgQWxiZXJzMRMwEQYJKoZIhvcNAQkB -FgRub25lMB4XDTE2MDMwMTA0MTcyNVoXDTE3MDcxNDA0MTcyNVowbTELMAkGA1UE -BhMCVVMxETAPBgNVBAgMCENvbG9yYWRvMQ8wDQYDVQQHDAZEZW52ZXIxDDAKBgNV -BAoMA01SSjEXMBUGA1UEAwwOTWljaGFlbCBBbGJlcnMxEzARBgkqhkiG9w0BCQEW -BG5vbmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWq7UF6h6IeWtp -37Hzddfgh/B7ZHqv3jBem8yGhgTvj0rwAZ28XPwi9iQ8o6/8OFGVfZ/48WDi91xU -qUQwtf+ivT6Sp6zMlm3X1PKpRacQSJSODw4jZEV3Im1/dg5tXhxc/gHmfJaIhOEK -5l2VYgzwxND++tJx2REEXd/5rKo6LJdYfTXmavVKblx6yTmwu0hUvPsna4Th0kJU -tMLXoM/bB95G6fUu6BxUx8oI7Pj8UIfKLJwEGhvc0ZOo6/oLESoezSu8mgLOyu1n -iXZQzmjpdaGWhs5xT5kZgzPu/vkYyQ8jWTtbXyTfSlefRFaiNT0CZzzsBbKVlHeS -h1I6kcsFAgMBAAGjUDBOMB0GA1UdDgQWBBRpFtDA1wCZDPPs+i8KjSxFZTwjzDAf -BgNVHSMEGDAWgBRpFtDA1wCZDPPs+i8KjSxFZTwjzDAMBgNVHRMEBTADAQH/MA0G -CSqGSIb3DQEBCwUAA4IBAQAvoxhDOVBZUeBT/xJOdLAokXicWBP1QtTGuZxLf5VR -Y4BkcgpDuo/UQjDRN+kDWLZwypn47EOhacbLBazv+6La7TJDnH/gQqWQOqyYqbyL -RON8heFWnbMy3uMrNHrDEi2mf2SU+hmPSNWLM5S+4NdgB0xVjADJE0ugvebrMMr7 -7tUk0fPhYg0yi+S3eFdpJkfV7NURrOjpXMyQLwCt1q4w8hEoGFYnx22BQWwVE3Q6 -FV1a5e1xvwgaDluQ8bnciueQzm76C7FgTp0qbUdfWfiMzs0ziMkzUkh87Jbdwq0v -e3c7IGKfHt1xZVbR6JUbK/wKGPUTQdiwAiYVdfowBl0D ------END CERTIFICATE----- diff --git a/CameraFeed/keys/key.pem b/CameraFeed/keys/key.pem deleted file mode 100755 index de2e3921df..0000000000 --- a/CameraFeed/keys/key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCWq7UF6h6IeWtp -37Hzddfgh/B7ZHqv3jBem8yGhgTvj0rwAZ28XPwi9iQ8o6/8OFGVfZ/48WDi91xU -qUQwtf+ivT6Sp6zMlm3X1PKpRacQSJSODw4jZEV3Im1/dg5tXhxc/gHmfJaIhOEK -5l2VYgzwxND++tJx2REEXd/5rKo6LJdYfTXmavVKblx6yTmwu0hUvPsna4Th0kJU -tMLXoM/bB95G6fUu6BxUx8oI7Pj8UIfKLJwEGhvc0ZOo6/oLESoezSu8mgLOyu1n -iXZQzmjpdaGWhs5xT5kZgzPu/vkYyQ8jWTtbXyTfSlefRFaiNT0CZzzsBbKVlHeS -h1I6kcsFAgMBAAECggEBAJVzMBXzyeF4/pB/8FUbeMwgSus6GW/EppnRVCfDW7X7 -nks+byVd3kMXf44elvtJKbNsbndRhdbboVvgoeDnRfA4Yo65fu9X7xB9C03X5wSB -2cinKlD0ruqi3ZXmlhzpkpyy31OuFOrJUyeqpPz9yvQvZVblmESRGQ4Jx5YyLI+S -EcpzUKvv2H/t7ykqv33JaDJZ5KabtJzezbSnao0GDJHKsEaZVPXgS2Je1vxAw4GX -UdPqdISuDPM2vw6aEew8AmH0CGJPB1Goo7QaVrT+sgAi6SAMZDukSrbR5g68p5At -Fhi+qDDfdrZqR5sQ9hCmTep4hPr8DKZmp6dZGmNw6ukCgYEAx42JPBsUgPXPMUkW -PualQM9onhcoftZDKauErDagN9ocXFaFfPpSo8SVCYOfpY9pCPGbmAAhZj7mlh0S -tCNk5G1lNLiEgbegZTkKGzdqbCa7ThyUZ3dx5imw8NDuaJ5bYgls7uYRThz8DIJR -9jkO+sSMXGnIbnyeUslXBgJ2SeMCgYEAwUpovmso0WmOErXoyYeRav2BEp+lhL1H -nhn+gsh5tyuanmgKkofgqPe+51KKfEkkogAcmpKJS1KXWBn6ZfW7cn4xhfeycmIH -kGzSWBnZiSB/jIcaHuyp1zfSty22PzFPpqrFbQs8l3dkNHSxeOhBCRzOBG8coujN -IdMN92f5S/cCgYAB/QIKDEcHBev7lLvZAplQ7QAg2yA3K1Fd/+yBfsXX6J9xuBb6 -aNAb+6B0iNA1aRll0mp3eEDc8PGBO2btTpD5ybFBdjkzxa2edJQKM2InE7e4DobY -BROodG/j5mEJv9IvRuLD+pzfh2Bni4DfkC/7BaxUW2V43FsDfigU0j91ewKBgEsv -+6CepIkZK0fB9SR3lKxuogexjDwfOL2aVPNgsl/7GTEnPX2UV2LCxELNS8te1F4j -9vx1pexj2zVNHacNuHWn+vGm0YZG9bRLcGMO4xzBRHxQjWucGdD7CP9yS6M3NkmZ -wiRRq6crrRHulp52kd3Ok6EL67K/JhRTOeqUSlgvAoGBAK6vKHL3prEs0bDmTEd5 -vPyBofCVXSayK4y8VcwLzwbQIF2QoZjm9UUd3sv29z1zt8UV7KKK/lrTvXMqpIfm -FTNCpOMV+DBpi5oPyolErRhdpu2uDUYo31o6zJa7AGa3ShHF95kqrjJjabqmWsTi -KgkZQwgZxXXY7ATNdSdHfbWP ------END PRIVATE KEY----- diff --git a/CameraFeed/src/JPEGMongoDBWriter.cpp b/CameraFeed/src/JPEGMongoDBWriter.cpp index bba8ba5a6c..3a33a3b1a2 100644 --- a/CameraFeed/src/JPEGMongoDBWriter.cpp +++ b/CameraFeed/src/JPEGMongoDBWriter.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -14,6 +15,7 @@ JPEGMongoDBWriter::JPEGMongoDBWriter(const std::string &theURI, int theDebug) : myInstance{}, myClient{mongocxx::uri{theURI}}, + // Database: CSCI5799, Collection: CameraFeed myCollection{myClient["CSCI5799"]["CameraFeed"]}, myDebug(theDebug) { @@ -36,20 +38,14 @@ void JPEGMongoDBWriter::handleJPEG(const char *theJPEG, size_t theSize) doc.append(bsoncxx::builder::basic::kvp("camera_id", "Camera123")); - struct timeval currentTime; - gettimeofday(¤tTime, 0); + std::chrono::milliseconds ms = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()); - doc.append(bsoncxx::builder::basic::kvp("unix_time", - [&](bsoncxx::builder::basic::sub_document subdoc) - { - subdoc.append( - bsoncxx::builder::basic::kvp( - "seconds", bsoncxx::types::b_int64{currentTime.tv_sec})); - subdoc.append( - bsoncxx::builder::basic::kvp( - "microseconds", - bsoncxx::types::b_int64{currentTime.tv_usec})); - })); + // The RetrieveVideo microservice works much better with raw milliseconds + // since epoch (usual 1970 date). + doc.append(bsoncxx::builder::basic::kvp( + "msSinceEpoch", bsoncxx::types::b_int64{ms.count()})); doc.append(bsoncxx::builder::basic::kvp( "jpeg", diff --git a/CameraFeed/src/main.cpp b/CameraFeed/src/main.cpp index 4e1ddbc4c7..e8377ed90c 100644 --- a/CameraFeed/src/main.cpp +++ b/CameraFeed/src/main.cpp @@ -8,8 +8,6 @@ #include "JPEGFileWriter.h" #include "JPEGMongoDBWriter.h" -//http://stackoverflow.com/questions/12797647/ffmpeg-how-to-mux-mjpeg-encoded-data-into-mp4-or-avi-container-c -//http://video.stackexchange.com/questions/7903/how-to-losslessly-encode-a-jpg-image-sequence-to-a-video-in-ffmpeg int main(int argc, char **argv) { try diff --git a/RetrieveVideo/.gitignore b/RetrieveVideo/.gitignore new file mode 100644 index 0000000000..042ccf2712 --- /dev/null +++ b/RetrieveVideo/.gitignore @@ -0,0 +1,2 @@ +node_modules +*~ diff --git a/RetrieveVideo/Dockerfile b/RetrieveVideo/Dockerfile new file mode 100644 index 0000000000..6874c6939e --- /dev/null +++ b/RetrieveVideo/Dockerfile @@ -0,0 +1,19 @@ +FROM node:5.10.1 + +# Need to run this to see what's in the various sources +RUN apt-get -qq update + +# Get avconv +RUN apt-get install -y libav-tools + +# Create app directory +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +COPY package.json /usr/src/app/ +RUN npm install +COPY . /usr/src/app + +EXPOSE 8080 + +CMD ["npm", "start"] diff --git a/RetrieveVideo/README.txt b/RetrieveVideo/README.txt new file mode 100644 index 0000000000..8f6c2eb9a5 --- /dev/null +++ b/RetrieveVideo/README.txt @@ -0,0 +1,23 @@ +###################################### + README For RetrieveVideo Microservice +###################################### + +Description +=========== +This microservice pulls recorded video and serves it to the user as a single +MP4 file. + +API +=== +/video?[args...] + +API Versions +------------ ++V1 + /video?start=[start time]&stop=[stop time]&camera_id=[camera id] + + start Video start time. Format is one accepted by Javascript Date.parse. + stop Video stop time. Format is one accepted by Javascript Date.parse. + + Example: + /start=2016-04-10T00:00:00&stop=2016-04-11T00:00:00.0&camera_id=Camera123 diff --git a/RetrieveVideo/RetrieveVideo.js b/RetrieveVideo/RetrieveVideo.js new file mode 100644 index 0000000000..c93c414db6 --- /dev/null +++ b/RetrieveVideo/RetrieveVideo.js @@ -0,0 +1,197 @@ +'use strict'; + +var express = require('express'); +var app = express(); +var mongoClient = require('mongodb').MongoClient; +var sprintf = require('sprintf-js'); +var avconv = require('avconv'); +const fs = require('fs'); + +const debugFlag = process.env.DEBUG; + +var debug = function(debugString) { + if (debugFlag) { + console.log(debugString); + } +}; + +var dateConvert = function(req, res, next) { + var start = req.query.start; + var stop = req.query.stop; + debug("Converting... start: " + start + ", stop: " + stop); + if (start && stop) { + req.query.start = Date.parse(start); + req.query.stop = Date.parse(stop); + if (isNaN(req.query.start) || isNaN(req.query.stop)) + { + next({code: 400, message: "Invalid start or stop date format."}); + } + else { + debug(" start: " + req.query.start + ", stop: " + req.query.stop); + next(); + } + } + else if (! start) { + next({code: 400, message: "Missing required 'start' parameter."}); + } + else if (! stop) { + next({code: 400, message: "Missing required 'stop' parameter."}); + } + else { + next({code: 400, + message: "Missing required 'start' and 'stop' parameters."}); + } +}; + +app.use(dateConvert); + +var cameraIdChecker = function(req, res, next) { + debug("Checking camera_id parameter..."); + if (req.query.camera_id) { + next(); + } + else + { + next({code: 400, message: "Missing required 'camera_id' parameter."}); + } +}; + +app.use(cameraIdChecker); + +var jpegRetriever = function(req, res, next) { + debug("Retrieving video..."); + var user = "dummyUser"; // TODO: need user + var template = "/tmp/RetrieveVideo_" + req.query.camera_id + '_' + user; + req.tmpDir = fs.mkdtempSync(template); + req.fileList = new Array(); + + // TODO: need actual host/port + var url = "mongodb://localhost:27017/CSCI5799"; + + mongoClient.connect(url, function(err, db) { + if (err) { + next("Error connecting to MongoDB " + url + ". Error: " + err); + } + else { + debug("Connected to MongoDB " + url + "."); + var collection = db.collection('CameraFeed'); + // https://docs.mongodb.org/getting-started/node/query/ + // https://docs.mongodb.org/manual/reference/operator/query + var query = { + "user": user, + "camera_id": req.query.camera_id, + $and : [ + { $and: [ {"msSinceEpoch": {$gte: req.query.start}}, + {"msSinceEpoch": {$lte: req.query.stop}} ] } ] + }; + debug("Query: " + JSON.stringify(query)); + var cursor = collection.find(query).sort({"msSinceEpoch": 1}); + var numberImages = 0; + var width = 0; + cursor.count(function(err, count) { + if (! err) { + numberImages = count; + width = Math.ceil(Math.log(numberImages) / Math.log(10)); + req.fileFormat = "input%0" + width + "d.jpg"; + debug("Found " + numberImages + " images..."); + } + }); + + if (numberImages > 0) { + var imageNumber = 0; + cursor.each(function(err, doc) { + if (err) { + next("Error retrieving video from DB: " + err); + } + else if (doc) { + imageNumber++; + var fileName = sprintf.sprintf(req.fileFormat, imageNumber); + fileName = req.tmpDir + "/" + fileName; + var jpegFile = fs.createWriteStream(fileName); + jpegFile.write(doc.jpeg.buffer); + jpegFile.end(); + req.fileList.push(fileName); + } + else { + debug("Done writing " + numberImages + " image(s) to disk..."); + db.close(); + next(); + } + }); + } + else { + next({code: 400, message: "No video found for given time range."}); + } + } + }); +}; + +app.use(jpegRetriever); + +var mpeg4Builder = function(req, res, next) { + debug("Building MP4 in " + req.tmpDir + "..."); + req.tmpFile = req.tmpDir + "/" + "Video.mp4"; + // avconv -r 25 -i input%03d.jpg test.mp4 + var avconvParams = [ + "-r", 25, + "-i", req.tmpDir + "/" + req.fileFormat, + req.tmpFile + ]; + debug(avconvParams); + var avconvStream = avconv(avconvParams); + avconvStream.on('exit', function(exitCode, signal, metadata) { + if (exitCode == 0) { + debug("Finished encoding movie..."); + next(); + } + else { + next("Error with avconv: " + exitCode + ", signal: " + signal + + ", meta: " + metadata); + } + }); +}; + +app.use(mpeg4Builder); + +var errorHandler = function(err, req, res, next) { + var code = 500; + var message = "Unhandled error."; + if (typeof err == "object") { + if (err["code"]) { + code = err["code"]; + } + if (err["message"]) { + message = err["message"]; + } + } + else { + message = err; + } + res.status(code).send(message); +}; + +app.use(errorHandler); + +// TODO: version +app.get('/video', function (req, res) { + debug("Downloading video..."); + res.download(req.tmpFile, "Video.mp4", function(err){ + if (err) { + console.log("Error: " + err); + } + else { + debug("Video downloaded..."); + } + for (var ii = 0; ii < req.fileList.length; ++ii) { + fs.unlinkSync(req.fileList[ii]); + } + fs.unlinkSync(req.tmpFile); + fs.rmdirSync(req.tmpDir); + }); +}); + +const PORT = 8080; + +app.listen(PORT, function () { + debug('RetrieveVideo listening on port ' + PORT + '!'); +}); diff --git a/RetrieveVideo/package.json b/RetrieveVideo/package.json new file mode 100644 index 0000000000..4aaad21196 --- /dev/null +++ b/RetrieveVideo/package.json @@ -0,0 +1,18 @@ +{ + "name": "retrieve_video", + "version": "0.0.5", + "description": "Retrieve saved video", + "main": "RetrieveVideo.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "scripts": {"start": "node RetrieveVideo.js"}, + "author": "Michael Albers", + "license": "ISC", + "dependencies": { + "avconv": "^3.0.2", + "express": "^4.13.4", + "mongodb": "^2.1.16", + "sprintf-js": "^1.0.3" + } +} From aa91226c69976c33a46e269c934dc7c997bc4d11 Mon Sep 17 00:00:00 2001 From: Ramiro Arenivar Date: Sun, 10 Apr 2016 21:16:45 -0600 Subject: [PATCH 33/81] edited availablecameras collections, this collection is now client-only. Fixed issue with transitioning between templates. Moved data_collections file to a client only directory, all collections should be local since all of the apps data will be stored in an external database. Added feature to delete all collections on logout --- camera5799/client/data_collections.js | 1 + .../client/templates/includes/header.js | 10 + .../templates/views/browse_cameras.html | 3 +- .../client/templates/views/browse_cameras.js | 4 +- .../client/templates/views/browse_videos.html | 2 +- .../client/templates/views/camera_detail.html | 2 +- .../client/templates/views/home_page.html | 200 ++++++++---------- camera5799/lib/data_collections.js | 1 - camera5799/server/api_calls.js | 5 +- 9 files changed, 102 insertions(+), 126 deletions(-) create mode 100644 camera5799/client/data_collections.js delete mode 100644 camera5799/lib/data_collections.js diff --git a/camera5799/client/data_collections.js b/camera5799/client/data_collections.js new file mode 100644 index 0000000000..70e4fb7427 --- /dev/null +++ b/camera5799/client/data_collections.js @@ -0,0 +1 @@ +AvailableCameras = new Mongo.Collection(null); \ No newline at end of file diff --git a/camera5799/client/templates/includes/header.js b/camera5799/client/templates/includes/header.js index 54d0508624..0c95f83331 100755 --- a/camera5799/client/templates/includes/header.js +++ b/camera5799/client/templates/includes/header.js @@ -21,6 +21,16 @@ Template.header.events({ e.preventDefault(); localStorage.removeItem('login_response'); Session.set('login_response', null); + + // remove all of the client collections on logout + var globalObject=Meteor.isClient?window:global; + for(var property in globalObject){ + var object=globalObject[property]; + if(object instanceof Meteor.Collection){ + object.remove({}); + } + } + Router.go('homePage'); } }); \ No newline at end of file diff --git a/camera5799/client/templates/views/browse_cameras.html b/camera5799/client/templates/views/browse_cameras.html index afb4034080..253e1815bd 100644 --- a/camera5799/client/templates/views/browse_cameras.html +++ b/camera5799/client/templates/views/browse_cameras.html @@ -1,13 +1,12 @@