diff options
| -rw-r--r-- | cli/main.go | 2 | ||||
| -rw-r--r-- | src/fsm.go | 261 | ||||
| -rw-r--r-- | src/oauth.go | 9 | ||||
| -rw-r--r-- | src/server.go | 4 | ||||
| -rw-r--r-- | src/server_test.go | 4 | ||||
| -rw-r--r-- | src/state.go | 10 |
6 files changed, 66 insertions, 224 deletions
diff --git a/cli/main.go b/cli/main.go index 6e12848..a9a1167 100644 --- a/cli/main.go +++ b/cli/main.go @@ -20,7 +20,7 @@ func openBrowser(urlString string) { func logState(oldState string, newState string, data string) { fmt.Printf("State: %s -> State: %s with data %s\n", oldState, newState, data) - if newState == "SERVER_OAUTH_STARTED" { + if newState == "OAuth_Started" { openBrowser(data) } } @@ -1,7 +1,6 @@ package eduvpn import ( - "errors" "fmt" "os" ) @@ -9,159 +8,66 @@ import ( type FSMStateID int8 const ( - // Registered means the app is registered with the wrapper - APP_REGISTERED FSMStateID = iota - // Deregistered means the app is not registered with the wrapper - APP_DEREGISTERED + DEREGISTERED FSMStateID = iota + + // No Server means the user has not chosen a server yet + NO_SERVER - // We have the states where a server is chosen or not - // When no server is chosen, we have no substate - CONFIG_NOSERVER + // Chosen Server means the user has chosen a server to connect to + CHOSEN_SERVER - // When a server is chosen we have the remaining states as substates - CONFIG_CHOSENSERVER + // OAuth Started means the OAuth process has started + OAUTH_STARTED - // The states for when the server is authenticated - // The SERVER_AUTHENTICATED is the parent state - // While SERVER_CONNECTED and SERVER_DISCONNECTED are substatse - SERVER_AUTHENTICATED - SERVER_CONNECTED - SERVER_DISCONNECTED + // Authenticated means the OAuth process has finished and the user is now authenticated with the server + AUTHENTICATED - // The states for when the server is not authenticated - // The SERVER_NOT_AUTHENTICATED is the parent state - // While SERVER_INITIALIZED, SERVER_OAUTH_STARTED and SERVER_OAUTH_FINISHED are substates - SERVER_NOT_AUTHENTICATED - SERVER_INITIALIZED - SERVER_OAUTH_STARTED - SERVER_OAUTH_FINISHED + // Connected means the user has been connected to the server + CONNECTED ) func (s FSMStateID) String() string { switch s { - case APP_REGISTERED: - return "APP_REGISTERED" - case APP_DEREGISTERED: - return "APP_DEREGISTERED" - case CONFIG_NOSERVER: - return "CONFIG_NOSERVER" - case CONFIG_CHOSENSERVER: - return "CONFIG_CHOSENSERVER" - case SERVER_AUTHENTICATED: - return "SERVER_AUTHENTICATED" - case SERVER_CONNECTED: - return "SERVER_CONNECTED" - case SERVER_DISCONNECTED: - return "SERVER_DISCONNECTED" - case SERVER_NOT_AUTHENTICATED: - return "SERVER_NOT_AUTHENTICATED" - case SERVER_INITIALIZED: - return "SERVER_INITIALIZED" - case SERVER_OAUTH_STARTED: - return "SERVER_OAUTH_STARTED" - case SERVER_OAUTH_FINISHED: - return "SERVER_OAUTH_FINISHED" + case DEREGISTERED: + return "Deregistered" + case NO_SERVER: + return "No_Server" + case CHOSEN_SERVER: + return "Chosen_Server" + case OAUTH_STARTED: + return "OAuth_Started" + case AUTHENTICATED: + return "Authenticated" + case CONNECTED: + return "Connected" default: panic("unknown conversion of state to string") } } type ( - FSMStates map[FSMStateID]*FSMState FSMTransitions []FSMStateID + FSMStates map[FSMStateID]FSMTransitions ) -type FSMState struct { - Sub *FSM - Transition FSMTransitions - - // When Locked=True it cannot go to the parent state and transition away - Locked bool -} - type FSM struct { States FSMStates Current FSMStateID } -func (fsmState *FSMState) hasTransition(check FSMStateID) bool { - for _, state := range fsmState.Transition { - if state == check { +func (eduvpn *VPNState) HasTransition(check FSMStateID) bool { + for _, transition_state := range eduvpn.FSM.States[eduvpn.FSM.Current] { + if transition_state == check { return true } } - return false -} - -func (eduvpn *VPNState) getCurrentState() (*FSMState, error) { - state, hasState := eduvpn.FSM.States[eduvpn.FSM.Current] - - if !hasState { - return nil, errors.New("Cannot get current state") - } - - return state, nil -} - -func FindFSMState(state FSMStateID, fsm *FSM) *FSM { - if fsm == nil { - return nil - } - - // Check if the state is in the current fsm - retrievedState, hasState := fsm.States[state] - - // Otherwise we need to go to the sub states - if !hasState || retrievedState == nil { - return FindFSMState(state, fsm.States[fsm.Current].Sub) - } else { - return fsm - } -} - -func (eduvpn *VPNState) IsInFSMState(check FSMStateID) bool { - return eduvpn.FSM.Current == check -} - -func (eduvpn *VPNState) findTransition(check FSMStateID) (*FSM, bool) { - fsm := FindFSMState(check, eduvpn.FSM) - - if fsm == nil { - return nil, false - } - - subStates := fsm.States[fsm.Current].Sub - - if subStates != nil { - if subStates.States[subStates.Current].Locked { - return nil, false - } - } - for _, val := range fsm.States[fsm.Current].Transition { - if val == check { - return fsm, true - } - } - - return nil, false -} - -func (eduvpn *VPNState) HasTransition(check FSMStateID) bool { - fsm, ok := eduvpn.findTransition(check) - - return ok && fsm != nil + return false } func (eduvpn *VPNState) InState(check FSMStateID) bool { - fsm := FindFSMState(check, eduvpn.FSM) - - if fsm == nil { - return false - } - - return fsm.Current == check + return check == eduvpn.FSM.Current } func (eduvpn *VPNState) writeGraph() { @@ -178,11 +84,11 @@ func (eduvpn *VPNState) writeGraph() { } func (eduvpn *VPNState) GoTransition(newState FSMStateID, data string) bool { - fsm, ok := eduvpn.findTransition(newState) + ok := eduvpn.HasTransition(newState) if ok { - oldState := fsm.Current - fsm.Current = newState + oldState := eduvpn.FSM.Current + eduvpn.FSM.Current = newState if eduvpn.Debug { eduvpn.writeGraph() } @@ -192,94 +98,31 @@ func (eduvpn *VPNState) GoTransition(newState FSMStateID, data string) bool { return ok } -func getGraphviz(fsm *FSM, graph string, current bool) string { - if fsm == nil { - return graph - } - - for name, state := range fsm.States { - for _, transition := range state.Transition { - graph += "\n" + "cluster_" + name.String() + " -> cluster_" + transition.String() - } - - graph += "\nsubgraph cluster_" + name.String() + "{\n" - if state.Locked { - graph += "style=\"dotted\"\n" - } else { - graph += "style=\"\"\n" - } - if fsm.Current == name { - color := "orange" - if current { - color = "blue" - } - graph += fmt.Sprintf("color=\"%s\"\n", color) - graph += fmt.Sprintf("fontcolor=\"%s\"\n", color) - } else { - graph += "color=\"\"\n" - graph += "fontcolor=\"\"\n" +func (eduvpn *VPNState) GenerateGraph() string { + graph := `digraph eduvpn_fsm { +nodesep = 2; +rankdir = LR;` + graph += "\nnode[color=blue]; " + eduvpn.FSM.Current.String() + ";\n" + graph += "node [color=black];\n" + for state, transitions := range eduvpn.FSM.States { + for _, transition_state := range transitions { + graph += state.String() + " -> " + transition_state.String() + "\n" } - graph += "label=" + name.String() - graph = getGraphviz(state.Sub, graph, current && fsm.Current == name) - graph += "\n}" } - return graph -} - -func (eduvpn *VPNState) GenerateGraph() string { - graph := `digraph fsm_with_legend { -nodesep=2 -subgraph fsm { -nodesep=2` - graph = getGraphviz(eduvpn.FSM, graph, true) - graph += "\n}" - graph += `graph [labelloc="b" labeljust="r" label=< -<TABLE BORDER="2" CELLBORDER="1" CELLSPACING="0"> -<TR><TD BGCOLOR="BLUE"><font color="white">The current state</font></TD></TR> -<TR><TD BGCOLOR="ORANGE">A state that is not the current state but will be once the parent state becomes the current</TD></TR> -</TABLE>>]; -}` - + graph += "}" return graph } func (eduvpn *VPNState) InitializeFSM() { - // The states when a server is authenticated - serverAuthenticated := &FSMState{Sub: &FSM{States: FSMStates{ - SERVER_DISCONNECTED: {Transition: FSMTransitions{SERVER_CONNECTED}}, - SERVER_CONNECTED: {Transition: FSMTransitions{SERVER_DISCONNECTED}}, - }, Current: SERVER_DISCONNECTED}, Transition: FSMTransitions{SERVER_NOT_AUTHENTICATED}} - - // The states when a server is not authenticated - serverNotAuthenticated := &FSMState{Sub: &FSM{States: FSMStates{ - // In this state we cannot exit to the parent state - // As the parent state can go to authenticated - SERVER_INITIALIZED: {Transition: FSMTransitions{SERVER_OAUTH_STARTED}, Locked: true}, - - // The state that indicates oauth is in progress - SERVER_OAUTH_STARTED: {Transition: FSMTransitions{SERVER_OAUTH_FINISHED}, Locked: true}, - SERVER_OAUTH_FINISHED: {Transition: FSMTransitions{SERVER_OAUTH_STARTED}}, - }, Current: SERVER_INITIALIZED}, Transition: FSMTransitions{SERVER_AUTHENTICATED}} - - // The states of the server, it has authenticated and not authenticated ass sub states - serverStates := &FSMState{Sub: &FSM{States: FSMStates{ - SERVER_AUTHENTICATED: serverAuthenticated, - SERVER_NOT_AUTHENTICATED: serverNotAuthenticated, - }, Current: SERVER_NOT_AUTHENTICATED}, Transition: FSMTransitions{CONFIG_NOSERVER}} - - // The state when a server is registered - registeredState := &FSMState{Sub: &FSM{States: FSMStates{ - // When no server has been chosen, we have no sub states - CONFIG_NOSERVER: {Transition: FSMTransitions{CONFIG_CHOSENSERVER}}, - // A server has been chosen, it has substates such as oauth, connected and disconnected - CONFIG_CHOSENSERVER: serverStates, - }, Current: CONFIG_NOSERVER}, Transition: FSMTransitions{APP_DEREGISTERED}} - - deregisteredState := &FSMState{Transition: FSMTransitions{APP_REGISTERED}} - eduvpn.FSM = &FSM{ - States: FSMStates{ - APP_REGISTERED: registeredState, APP_DEREGISTERED: deregisteredState, - }, Current: APP_DEREGISTERED, + States: FSMStates { + DEREGISTERED: {NO_SERVER}, + NO_SERVER: {CHOSEN_SERVER}, + CHOSEN_SERVER: {AUTHENTICATED, OAUTH_STARTED}, + OAUTH_STARTED: {AUTHENTICATED}, + AUTHENTICATED: {CONNECTED}, + CONNECTED: {AUTHENTICATED}, + }, + Current: DEREGISTERED, } } diff --git a/src/oauth.go b/src/oauth.go index 8656979..836165a 100644 --- a/src/oauth.go +++ b/src/oauth.go @@ -227,7 +227,7 @@ func (oauth *OAuth) Callback(w http.ResponseWriter, req *http.Request) { // It needs a vpn state that was gotten from `Register` // It returns the authurl for the browser and an error if present func (eduvpn *VPNState) InitializeOAuth() error { - if !eduvpn.HasTransition(SERVER_OAUTH_STARTED) { + if !eduvpn.HasTransition(OAUTH_STARTED) { return errors.New("Failed starting oauth, invalid state") } // Generate the state @@ -262,13 +262,13 @@ func (eduvpn *VPNState) InitializeOAuth() error { // Fill the struct with the necessary fields filled for the next call to getting the HTTP client oauthSession := &OAuthExchangeSession{ClientID: eduvpn.Name, State: state, Verifier: verifier} eduvpn.Server.OAuth = &OAuth{TokenURL: eduvpn.Server.Endpoints.API.V3.Token, Session: oauthSession} - eduvpn.GoTransition(SERVER_OAUTH_STARTED, authURL) + eduvpn.GoTransition(OAUTH_STARTED, authURL) return nil } // Error definitions func (eduvpn *VPNState) FinishOAuth() error { - if !eduvpn.HasTransition(SERVER_OAUTH_FINISHED) { + if !eduvpn.HasTransition(AUTHENTICATED) { return errors.New("invalid state to finish oauth") } oauth := eduvpn.Server.OAuth @@ -276,8 +276,7 @@ func (eduvpn *VPNState) FinishOAuth() error { if tokenErr != nil { return tokenErr } - eduvpn.GoTransition(SERVER_OAUTH_FINISHED, "") - eduvpn.GoTransition(SERVER_AUTHENTICATED, "") + eduvpn.GoTransition(AUTHENTICATED, "") return nil } diff --git a/src/server.go b/src/server.go index f829610..b398183 100644 --- a/src/server.go +++ b/src/server.go @@ -42,7 +42,7 @@ type ServerEndpoints struct { } func (server *Server) Initialize(url string) error { - if !GetVPNState().HasTransition(CONFIG_CHOSENSERVER) { + if !GetVPNState().HasTransition(CHOSEN_SERVER) { return errors.New("cannot choose a server") } server.BaseURL = url @@ -50,7 +50,7 @@ func (server *Server) Initialize(url string) error { if endpointsErr != nil { return endpointsErr } - GetVPNState().GoTransition(CONFIG_CHOSENSERVER, "Chosen server") + GetVPNState().GoTransition(CHOSEN_SERVER, "Chosen server") return nil } diff --git a/src/server_test.go b/src/server_test.go index 7c64e3e..f914583 100644 --- a/src/server_test.go +++ b/src/server_test.go @@ -36,7 +36,7 @@ func LoginOAuthSelenium(t *testing.T, url string) { } func StateCallback(t *testing.T, oldState string, newState string, data string) { - if newState == "SERVER_OAUTH_STARTED" { + if newState == "OAuth_Started" { go LoginOAuthSelenium(t, data) } } @@ -66,7 +66,7 @@ func test_connect_oauth_parameter(t *testing.T, parameters URLParameters, expect http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} state.Register("org.eduvpn.app.linux", "configsnologin", func(oldState string, newState string, data string) { - if newState == "SERVER_OAUTH_STARTED" { + if newState == "OAuth_Started" { baseURL := "http://127.0.0.1:8000/callback" url, err := HTTPConstructURL(baseURL, parameters) if err != nil { diff --git a/src/state.go b/src/state.go index ec927f9..c6f0f79 100644 --- a/src/state.go +++ b/src/state.go @@ -28,7 +28,7 @@ type VPNState struct { func (state *VPNState) Register(name string, directory string, stateCallback func(string, string, string), debug bool) error { state.InitializeFSM() - if !state.HasTransition(APP_REGISTERED) { + if !state.InState(DEREGISTERED) { return errors.New("app already registered") } state.Name = name @@ -50,13 +50,13 @@ func (state *VPNState) Register(name string, directory string, stateCallback fun // This error can be safely ignored, as when the config does not load, the struct will not be filled state.Log(LOG_INFO, "Previous configuration not found") } - state.GoTransition(APP_REGISTERED, "HALLO") + state.GoTransition(NO_SERVER, "") return nil } func (state *VPNState) Deregister() error { - if !state.HasTransition(APP_DEREGISTERED) { - return errors.New("app cannot deregister") + if state.InState(DEREGISTERED) { + return errors.New("app already deregistered") } // Close the log file state.CloseLog() @@ -90,7 +90,7 @@ func (state *VPNState) Connect(url string) (string, error) { return "", configErr } - if !state.HasTransition(SERVER_CONNECTED) { + if !state.HasTransition(CONNECTED) { return "", errors.New("cannot connect to server, invalid state") } |
