Skip to content

Commit

Permalink
Merge pull request #211 from maryvilledev/auth
Browse files Browse the repository at this point in the history
Implements a handshake facilitator
  • Loading branch information
thebho authored Feb 17, 2017
2 parents 7b8367c + d6d5908 commit 0e4e873
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 61 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ teammembers/*
*~
*.swp
vendor/
credentials.sh
4 changes: 4 additions & 0 deletions controller/linkscontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ func (c LinksController) Put() error {
return fmt.Errorf("PUT requests not currently supported.")
}

func (c LinksController) Options() error {
return fmt.Errorf("OPTIONS requests not currently supported.")
}

func (c LinksController) performGet() error {
path := util.CheckForID(c.r.URL)
if path == "" {
Expand Down
1 change: 1 addition & 0 deletions controller/restinterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ type RESTController interface {
Post() error
Delete() error
Put() error
Options() error
Base() *BaseController
}
4 changes: 4 additions & 0 deletions controller/skilliconscontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ func (c SkillIconsController) Put() error {
return c.addSkillIcon()
}

func (c SkillIconsController) Options() error {
return fmt.Errorf("OPTIONS requests not currently supported.")
}

func (c SkillIconsController) performGet() error {
path := util.CheckForID(c.r.URL)
if path == "" {
Expand Down
16 changes: 10 additions & 6 deletions controller/skillreviewscontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ func (c SkillReviewsController) Put() error {
return c.updateSkillReview()
}

func (c SkillReviewsController) Options() error {
return fmt.Errorf("OPTIONS requests not currently supported.")
}

func (c *SkillReviewsController) performGet() error {
queries := c.r.URL.Query()
if skill_id := queries.Get("skill_id"); skill_id != "" {
Expand Down Expand Up @@ -194,13 +198,13 @@ func (c *SkillReviewsController) getSkillName(sr *model.SkillReview) (string,
func (c *SkillReviewsController) removeSkillReview() error {
body, _ := ioutil.ReadAll(c.r.Body)

skillReview := model.SkillReview{}
err := json.Unmarshal(body, &skillReview)
if err != nil {
c.Warn("Marshaling Error: ", errors.MarshalingError(err))
}
skillReview := model.SkillReview{}
err := json.Unmarshal(body, &skillReview)
if err != nil {
c.Warn("Marshaling Error: ", errors.MarshalingError(err))
}

err = c.session.Delete("skillreviews", skillReview.ID, data.NewCassandraQueryOptions("skill_id", skillReview.SkillID, false))
err = c.session.Delete("skillreviews", skillReview.ID, data.NewCassandraQueryOptions("skill_id", skillReview.SkillID, false))
// TODO Add skillid field to opts
if err != nil {
log.Printf("removeSkillReview() failed for the following reason:"+
Expand Down
4 changes: 4 additions & 0 deletions controller/skillscontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ func (c SkillsController) Put() error {
return fmt.Errorf("PUT requests not currently supported.")
}

func (c SkillsController) Options() error {
return fmt.Errorf("OPTIONS requests not currently supported.")
}

func (c SkillsController) performGet() error {
path := util.CheckForID(c.r.URL)
if path == "" {
Expand Down
4 changes: 4 additions & 0 deletions controller/teammemberscontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ func (c TeamMembersController) Put() error {
return fmt.Errorf("PUT requests nor currently supported.")
}

func (c TeamMembersController) Options() error {
return fmt.Errorf("OPTIONS requests not currently supported.")
}

func (c *TeamMembersController) performGet() error {
path := util.CheckForID(c.r.URL)
if path == "" {
Expand Down
4 changes: 4 additions & 0 deletions controller/tmskillscontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ func (c TMSkillsController) Put() error {
return c.updateTMSkill()
}

func (c TMSkillsController) Options() error {
return fmt.Errorf("OPTIONS requests not currently supported.")
}

func (c *TMSkillsController) performGet() error {
path := util.CheckForID(c.r.URL)
if path == "" {
Expand Down
96 changes: 76 additions & 20 deletions controller/userscontroller.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package controller

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"

"skilldirectory/errors"
"skilldirectory/model"
)
Expand All @@ -17,61 +23,111 @@ func (c UsersController) Base() *BaseController {
}

func (c UsersController) Get() error {
return fmt.Errorf("GET requests nor currently supported.")
return fmt.Errorf("GET requests not currently supported.")
}

func (c UsersController) Post() error {
return c.authenticateUser()
}

func (c UsersController) Delete() error {
return fmt.Errorf("DELETE requests nor currently supported.")
return fmt.Errorf("DELETE requests not currently supported.")
}

func (c UsersController) Put() error {
return fmt.Errorf("PUT requests nor currently supported.")
return fmt.Errorf("PUT requests not currently supported.")
}

func (c UsersController) Options() error {
return c.handleOptionsRequest()
}

func (c *UsersController) authenticateUser() error {
body, _ := ioutil.ReadAll(c.r.Body)

// Create a new User instance and unmarshal the request data into it
user := model.User{}
err := json.Unmarshal(body, &user)
// Get the access code and client_id from the request
credentials := model.AuthCredentials{}
err := json.Unmarshal(body, &credentials)
if err != nil {
return errors.MarshalingError(err)
}

if err = c.validatePOSTBody(&user); err != nil {
if err = c.validatePOSTBody(&credentials); err != nil {
return err
}

acc, err := c.getUserAccount(&user)
if err != nil {
return err
// Check that the supplied client_id matches the one we have

githubClientID, isSet := os.LookupEnv("GITHUB_CLIENT_ID")
if !isSet {
return errors.MissingCredentialsError(fmt.Errorf(
"Missing client ID credential"))
}
if credentials.Id != githubClientID {
return errors.InvalidPOSTBodyError(fmt.Errorf(
"Invalid client_id supplied"))
}

// Get the Github client secret
githubClientSecret, isSet := os.LookupEnv("GITHUB_CLIENT_SECRET")
if !isSet {
return errors.MissingCredentialsError(fmt.Errorf(
"Missing client secret credential"))
}
credentials.Secret = githubClientSecret

b, err := json.Marshal(acc)
// Get the access token from Github
response, err := c.getAccessToken(&credentials)
if err != nil {
return errors.MarshalingError(err)
return err
}

c.w.Write(b)
// Read the response body, and write it to the response
tokenBody, _ := ioutil.ReadAll(response.Body)
c.w.Write(tokenBody)

return nil
}

func (c *UsersController) getUserAccount(user *model.User) (model.UserAccount, error) {
if user.Login != "test" || user.Password != "test" {
return model.UserAccount{}, errors.InvalidLoginData(fmt.Errorf("Invalid login data provided"))
func (c *UsersController) handleOptionsRequest() error {
c.w.Header().Set("Access-Control-Allow-Origin", "*")
c.w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
c.w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, access-control-allow-methods")
c.w.Write([]byte(""))
return nil
}

func (c *UsersController) getAccessToken(credentials *model.AuthCredentials) (*http.Response, error) {
body, err := json.Marshal(credentials)
if err != nil {
return nil, errors.MarshalingError(err)
}
req, err := http.NewRequest(http.MethodPost, "https://github.com/login/oauth/access_token/", bytes.NewBuffer(body))
if err != nil {
return nil, err
}
return model.UserAccount{Login: "test", DisplayName: "Foo Bar"}, nil

req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Content-Length", strconv.Itoa(len(body)))

client := http.Client{}
// Return the response to the Client's request
return client.Do(req)
}

func (c *UsersController) validatePOSTBody(user *model.User) error {
if user.Login == "" || user.Password == "" {
func (c *UsersController) validatePOSTBody(credentials *model.AuthCredentials) error {
if credentials.Id == "" || credentials.Code == "" {
return errors.IncompletePOSTBodyError(fmt.Errorf(
"%q and %q fields must be non-empty", "Login", "Password"))
"%q and %q fields must be non-empty", "Id", "Code"))
}
return nil
}

// stripFileContents takes a []byte of a file's contents, and will convert it to a string,
// and then trim that string and return it
// Needed as some editors will automatically enter a newline at the end, which will cause
// the inequality check for the client_id to fail
func (c *UsersController) stripFileContents(b []byte) string {
return strings.TrimSpace(string(b))
}
88 changes: 53 additions & 35 deletions controller/userscontroller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,57 +14,85 @@ import (

func TestUsersControllerBase(t *testing.T) {
base := BaseController{}
tc := UsersController{BaseController: &base}
sc := UsersController{BaseController: &base}

if base != *tc.Base() {
t.Error("Expected Base() to return base pointer")
if base != *sc.Base() {
t.Error("Expecting Base() to return base pointer")
}
}

func TestPostUser(t *testing.T) {
body := getReaderForUserLogin("test", "test")
request := httptest.NewRequest(http.MethodPost, "/api.users", body)
tc := getUsersController(request, &data.MockDataAccessor{})
func TestGet(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "/api/users", bytes.NewBufferString(""))
sc := getUsersController(request, &data.MockErrorDataAccessor{})

err := tc.Post()
if err != nil {
t.Errorf("Post failed: %s", err.Error())
err := sc.Get()
if err == nil {
t.Errorf("Expected error: %s", err.Error())
}
}

func TestPostUser_NoLogin(t *testing.T) {
body := getReaderForUserLogin("", "test")
request := httptest.NewRequest(http.MethodPost, "/api/users", body)
tc := getTeamMembersController(request, &data.MockDataAccessor{})
func TestDelete(t *testing.T) {
request := httptest.NewRequest(http.MethodDelete, "/api/users", bytes.NewBufferString(""))
sc := getUsersController(request, &data.MockErrorDataAccessor{})

err := sc.Delete()
if err == nil {
t.Errorf("Expected error: %s", err.Error())
}
}

err := tc.Post()
func TestPut(t *testing.T) {
request := httptest.NewRequest(http.MethodPut, "/api/users", bytes.NewBufferString(""))
sc := getUsersController(request, &data.MockErrorDataAccessor{})

err := sc.Put()
if err == nil {
t.Errorf("Expected error due to empty %q field in User POST request.", "name")
t.Errorf("Expected error: %s", err.Error())
}
}

func TestPostUser_NoPassword(t *testing.T) {
body := getReaderForUserLogin("test", "")
func TestOptions(t *testing.T) {
request := httptest.NewRequest(http.MethodOptions, "/api/users", bytes.NewBufferString(""))
sc := getUsersController(request, &data.MockDataAccessor{})

err := sc.Options()
if err != nil {
t.Error(err)
}
}

func TestPostUser_NoCode(t *testing.T) {
body := getReaderForNewCredentials("", "")
request := httptest.NewRequest(http.MethodPost, "/api/users", body)
tc := getTeamMembersController(request, &data.MockDataAccessor{})
uc := getUsersController(request, &data.MockDataAccessor{})

err := tc.Post()
err := uc.Post()
if err == nil {
t.Errorf("Expected error due to empty %q field in User POST request.", "title")
t.Errorf("Expected error: %s", err.Error())
}
}

func TestPostUser_Error(t *testing.T) {
body := getReaderForUserLogin("test", "test")
func TestPostUser_NoClientID(t *testing.T) {
body := getReaderForNewCredentials("foobarbaz", "")
request := httptest.NewRequest(http.MethodPost, "/api/users", body)
tc := getTeamMembersController(request, &data.MockErrorDataAccessor{})
uc := getUsersController(request, &data.MockDataAccessor{})

err := tc.Post()
err := uc.Post()
if err == nil {
t.Errorf("Expected error: %s", err.Error())
}
}

/*
getReaderForNewUser is a helper function for a new Skill with the given id, name, and skillType.
This Skill is then marshaled into JSON. A new Reader is created and returned for the resulting []byte.
*/
func getReaderForNewCredentials(code, clientID string) *bytes.Reader {
newCredentials := model.AuthCredentials{Code: code, Id: clientID}
b, _ := json.Marshal(newCredentials)
return bytes.NewReader(b)
}

/*
getUsersController is a helper function for creating and initializing a new BaseController with
the given HTTP request and DataAccessor. Returns a new UsersController created with that BaseController.
Expand All @@ -74,13 +102,3 @@ func getUsersController(request *http.Request, dataAccessor data.DataAccess) Use
base.Init(httptest.NewRecorder(), request, dataAccessor, nil, logrus.New())
return UsersController{BaseController: &base}
}

/*
getReaderForNewUser is a helper function for a new User with the given login and password.
This User is then marshaled into JSON. A new Reader is created and returned for the resulting []byte.
*/
func getReaderForUserLogin(login, password string) *bytes.Reader {
newUser := model.NewUser(login, password)
b, _ := json.Marshal(newUser)
return bytes.NewReader(b)
}
1 change: 1 addition & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ type InvalidLinkTypeError error
type InvalidPUTBodyError error
type InvalidDataModelState error
type InvalidLoginData error
type MissingCredentialsError error
2 changes: 2 additions & 0 deletions handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ func Handler(w http.ResponseWriter, r *http.Request, cont controller.RESTControl
err = cont.Delete()
case http.MethodPut:
err = cont.Put()
case http.MethodOptions:
err = cont.Options()
}

var statusCode int
Expand Down
3 changes: 3 additions & 0 deletions make
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export CASSANDRA_KEYSPACE=skill_directory_keyspace
export DEBUG_FLAG=true
export FILE_SYSTEM=LOCAL

### Export Github credentials
source ./credentials.sh

### Parse all command line flags
for arg in "$@"
do
Expand Down
Loading

0 comments on commit 0e4e873

Please sign in to comment.