Showing posts with label golang. Show all posts
Showing posts with label golang. Show all posts

Thursday, November 28, 2019

Go Composition vs Inheritance

Go does not support inheritance, but sometimes using embedded structs can look a little like inheritance. I explore that feature to see how it differs.

Contents

Introduction

In lieu of inheritance, the Go language encourages composition by allowing one struct to be embedded in another struct in a way that allows calling methods defined on the embedded struct as if they are defined on the containing struct.

Note: In this post I occasionally use object-oriented terminology such as base class, subclass, and override. Please remember that Go does not support these concepts; I am using those terms here to show how thinking that way with Go can lead to problems.

For the examples that follow, I assume we are building a graphical editor that allows manipulating visual objects on the screen. We want to be able to draw those objects, and we want to be able to transform them with operations such as rotate, so we define an interface with those methods:

Note: For convenience, the final collected code used in this post is available on play.golang.org.
type shape interface { draw() rotate(radians float64) // translate and scale omitted for simplicity }
We write a function that will draw all our shapes:
func drawShapes(shapes []shape) { for _, s := range shapes { s.draw() } }

Base class

We define our "base class", called polygon, where we implement a draw method that we can invoke from our "subclasses":
type polygon struct { sides int angle float64 } func (p *polygon) draw() { fmt.Printf("draw polygon with sides=%d\n", p.sides) vertexDelta := 2*math.Pi / float64(p.sides) vertexAngle := p.angle x0 := math.Cos(vertexAngle) y0 := math.Sin(vertexAngle) for i := 0; i < p.sides; i++ { // Draw one side within unit circle, offset by p.angle. vertexAngle += vertexDelta x1 := math.Cos(vertexAngle) y1 := math.Sin(vertexAngle) fmt.Printf("draw from (%v, %v) to (%v, %v)\n", x0, y0, x1, y1) x0 = x1 y0 = y1 } } func (p* polygon) rotate(radians float64) { p.angle += radians }

Subclass

We define a couple of "subclasses", triangle and square, that "extend" our "base class", along with functions to create instances of those types:
type triangle struct { polygon } type square struct { polygon } func createTriangle() *triangle { return &triangle{ polygon { sides: 3, }, } } func createSquare() *square { return &square{ polygon { sides: 4, }, } }

Main and test

Finally, we write a couple of test functions to create a list of shapes and draw them, and a one-line main function that calls our test function.
package main import ( "fmt" "math" ) func createTestShapes() []shape { shapes := make([]shape, 0) shapes = append(shapes, createTriangle()) shapes = append(shapes, createSquare()) return shapes } func testDrawShapes() { drawShapes(createTestShapes()) } func main() { testDrawShapes() }
When we run this program, it produces the expected output:
draw polygon with sides=3 draw from (1.000, 0.000) to (-0.500, 0.866) draw from (-0.500, 0.866) to (-0.500, -0.866) draw from (-0.500, -0.866) to (1.000, -0.000) draw polygon with sides=4 draw from (1.000, 0.000) to (0.000, 1.000) draw from (0.000, 1.000) to (-1.000, 0.000) draw from (-1.000, 0.000) to (-0.000, -1.000) draw from (-0.000, -1.000) to (1.000, -0.000)
Note that we have not defined any methods on the triangle and square types, yet the compiler accepts them as implementing shape, as seen by the fact that we can store them in a slice of shape and we can invoke draw on them. Because we embedded polygon in triangle and square, without giving them field names, Go has promoted all of the methods in polygon into the namespaces of triangle and square, allowing draw to be called directly on an instance of type triangle or square.

So far, relying on an object-oriented mental model has not caused us problems. Let's keep going and see when it does.

Overriding

We add a typeName method to our shape interface and our "base class", polygon, and we "override" that method in our "subclasses", triangle and square:
type shape interface { draw() rotate(radians float64) // translate and scale omitted for simplicity typeName() string } func (p *polygon) typeName() string { return "polygon" } func (p *triangle) typeName() string { return "triangle" } func (p *square) typeName() string { return "square" }
We can test our typeName methods by pointing our main to a different test function:
func printShapeNames(shapes []shape) { for _, s := range shapes { fmt.Println(s.typeName()) } } func testShapeNames() { printShapeNames(createTestShapes()) } func main() { testShapeNames() }
This outputs:
triangle square
No problems yet.

Downcall

Let's add a method to our interface and "base class" that invokes the method that we are overriding, and a new test function to call it. This is sometimes referred to as a downcall, in that a superclass calls into the overriding method of a subclass that is below it in the class hierarchy.
type shape interface { draw() rotate(radians float64) // translate and scale omitted for simplicity typeName() string nameAndSides() string } func (p *polygon) nameAndSides() string { return fmt.Sprintf("%s (%d)", p.typeName(), p.sides) } func printShapeNamesAndSides(shapes []shape) { for _, s := range shapes { fmt.Println(s.nameAndSides()) } } func testShapeNamesAndSides() { printShapeNamesAndSides(createTestShapes()) } func main() { testShapeNamesAndSides() }
This outputs:
polygon (3) polygon (4)
Well, that doesn't look right. We wanted it to print triangle and square instead of polygon both times. Thinking of this as inheritance has led us astray.

Method promotion

So, what happened here? Why did printShapeNames work, but printShapeNamesAndSides did not? Let's dig into that.

The return value of createShapes is []shape, which is a slice of objects that implement the shape interface. Since the triangle and square types implement that interface, we can store instances of those types in that slice. But how is it that those types implement that interface when we didn't write those methods for those types? The answer is method promotion.

When we embed one type inside another without giving the internal type a field name, Go automatically promotes all unambiguous names from the embedded type to the containing type. Effectively, for each method in the embedded type whose name does not conflict with a method in the containing type or in any other embedded type within that container, Go creates a method on the containing type that turns around and calls that method on the embedded type. For example, when we embed polygon in triangle the compiler effectively creates this code:
func (t *triangle) typeName() string { return t.polygon.typeName() }
If the embedded type satisfies an interface, and there are no ambiguous method names, this promotion of all the methods of the embedded type makes the containing type also satisfy that interface. Let's explore this method promotion behavior. We create another struct type called thing that has a typeName method, embed it along with our previously defined polygon, which also has a typeName method, in a new type polygonThing, then try to assign an instance of that to a variable of type shape.
type thing struct{} func (t *thing) typeName() string { return "thing" } type polygonThing struct { polygon thing } func testPolygonThing() { p := &polygonThing{} p.draw() fmt.Println(p.typeName()) var s shape = p fmt.Println(s.typeName()) } func main() { testPolygonThing() }
When we compile this, we get these errors:
./comp.go:130:16: ambiguous selector p.typeName ./comp.go:131:7: polygonThing.typeName is ambiguous ./comp.go:131:7: cannot use p (type *polygonThing) as type shape in assignment: *polygonThing does not implement shape (missing typeName method)
where line 131 is the line where we are assigning to s.

From this error we can see that Go did not promote the typeName method from either of the embedded structs into polygonThing. But there was no error message about the call to draw, so it did promote that method from polygon, since it is not ambiguous.

If we comment out the embedded thing line from the definition of polygonThing, the code compiles. If, instead, we comment out the embedded polygon line, we get different errors:
./comp.go:129:4: p.draw undefined (type *polygonThing has no field or method draw) ./comp.go:131:7: cannot use p (type *polygonThing) as type shape in assignment: *polygonThing does not implement shape (missing draw method)
If we want to keep both embedded structs in our composite struct, there are a couple of ways we can resolve the ambiguity of typeName appearing in both embedded structs. The simplest is to assign a name to one of the embedded structs, converting it to a regular field. Instead of writing thing in the definition of polygonThing, we can write t thing. Go then does not attempt to promote the methods from thing into polygonThing, and the promotion of typeName from polygon into polygonThing is no longer ambiguous, so it succeeds.

Another possibility is to resolve the ambiguity by defining a typeName method directly on polygonThing. In this case, Go does not attempt to promote typeName from either of the embedded structs. We can call a method in an embedded struct by referring to that embedded struct as if it were a named field.
func (t *polygonThing) typeName() string { return t.polygon.typeName()+"Thing" }
With this definition, the program compiles and runs, outputting
draw polygon with sides=0 polygonThing polygonThing

Solution

Now that we understand how embedded structs work in Go, let's go back and reconsider what happened with our printShapeNamesAndSides function.

Assume one of the elements in our slice of shape is an instance of triangle. We call nameAndSides with that triangle as the receiver. Since we did not define nameAndSides on triangle, that calls the promoted version of that method. That promoted method turns around and calls nameAndSides on the embedded polygon, passing the embedded polygon as the receiver. In polygon.nameAndSides, it calls p.typeName, but p here is the receiver of the nameAndSides method, which is the polygon, not the triangle. So the call from nameAndSides to typeName call's the typeName method on polygon rather than on triangle.

With this understanding, let's update our code to make "overriding" work. The difference between the behavior we are seeing and what we would expect from a system with inheritance and overriding is that here our "base class" does not, by default, make calls to methods of the "subclass". It can't because the method in the "base class" has no reference to the type of the containing object. In order to implement a call to method in an instance of a "subclass" from polygon.nameAndSides, we need a reference to that instance, such as a triangle. We will do this by explicitly passing our shape as an argument, then calling the typeName method on that shape rather than on the receiver. By calling a method on a passed-in argument rather than the receiver, it is clear, when looking at that method in the "base class", that the call may be going to a different type of object than polygon.
type shape interface { ... nameAndSides(s shape) string } func (p *polygon) nameAndSides(s shape) string { return fmt.Sprintf("%s (%d)", s.typeName(), p.sides) } func printShapeNamesAndSides(shapes []shape) { for _, s := range shapes { fmt.Println(s.nameAndSides(s)) } }
With these changes, we get the expected output:
triangle (3) square (4)

Conclusion

The way Go promotes methods of embedded structs makes it have some of the characteristics of inheritance as defined in object-oriented programming. In particular, it allows for methods to be automatically promoted to the containing struct, and thus for interfaces to be automatically promoted to the containing struct. One key difference is that, when you override one of those promoted methods in the containing struct, the code in the embedded class does not automatically call the overridden method in the containing class, as happens in some object-oriented languages such as Java.

You may have heard of the fragile base class problem. A related issue, that can arise when there are downcalls from a superclass to an overridden method in a subclass, similar to the example here where I "overrode" the typeName method, might be termed the fragile subclass problem. If you are interested into digging into that, you can read Safely Creating Correct Subclasses without Seeing Superclass Code, a paper from OOPSLA 2000 that examines that issue. See section 4. The designers of Go chose not to implement inheritance, but instead to favor composition. Although some Go constructs can look a little like inheritance, it's better to start thinking about designing in Go using composition rather than trying to bend Go to do something like inheritance.

Friday, April 13, 2018

Golang Web Server Auth

An example of authentication and authorization in a simple web server written in go.

Contents

Background

As described in my previous blog post, I recently rewrote my image viewer desktop app as a web app, for which I wrote the web server in go.

Since I was adding a new potential attack vector, I wanted to add security; but since this is only available on my internal network, and it's not critically valuable data, I did not need enterprise-grade security. In this post I describe how I implemented a relatively simple authentication and authorization mechanism, in particular highlighting the features of go I used that made that easy to do. For a simple app such as this one, the third of the three As of security, auditing, can be done with simple logging if desired.

The code I present here is taken from the github repo for my mimsrv project, with links to specific commits and versions of various files. You can visit that project if you'd like to see more of the code than I present in this post.

Before Auth

Go has good support for writing simple web servers. The net.http package allows setting up a web server that routes requests based on path to specific functions. In the first commit for mimsrv, before there was any code for authentication or authorization, the http processing code looked like this:

In mimsrv.go:
func main() { ... mux := http.NewServeMux() ... mux.Handle("/api/", api.NewHandler(...)) ... log.Fatal(http.ListenAndServe(":8080", mux)) }
In api/api.go:
func NewHandler(c *Config) http.Handler { h := handler{config: c} mux := http.NewServeMux() mux.HandleFunc(h.apiPrefix("list"), h.list) mux.HandleFunc(h.apiPrefix("image"), h.image) mux.HandleFunc(h.apiPrefix("text"), h.text) return mux } func (h *handler) list(w http.ResponseWriter, r *http.Request) { ... }
The above two functions set up the routing and start the web server. The code in mimsrv.go creates a top-level router (mux) that routes any request with a path starting with "/api/" to the api handler that is created by the NewHandler function in api.go. The top-level router also defines routes for other top-level paths, such as "/ui/" for delivering the UI files.

The api code in turn sets up the second-level routing for all of the paths within /api (the h.apiPrefix function adds "/api/" to its argument). So when I make a request with the path /api/list, the main mux passes the request to the api mux, which then calls the h.list function.

Adding Authentication

To implement authentication in mimsrv, I added a new "auth" package with three files, and modified mimsrv.go to use that new auth package. The most interesting part of this change is that it implements the enforcement of the constraint that all requests to any path starting with "/api/" must be authenticated, yet I did not have to make any changes to any of the api code that services those requests.

When I originally wrote my request routing code, it could have been simpler if I had defined everything in one mux. I didn't do that because I think the approach I took provides better modularity, but in addition, that structure made it easy for me to require authentication for all of the api calls.

The authentication code itself is not trivial, but wiring that code into the request routing to enforce authentication for whole chunks of the request path space was. I wrote a wrapper function and inserted it in the middle of the request-handling flow for requests where I wanted to require authentication.

To wire in the authentication requirement for all requests starting with "/api/", I changed mimsrv.go to replace this line:
mux.Handle("/api/", api.NewHandler(...))
with these lines:
apiHandler := api.NewHandler(...)) mux.Handle("/api/", authHandler.RequireAuth(apiHandler))
Here is the RequireAuth method from the newly added auth.go:
func (h *Handler) RequireAuth(httpHandler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){ token := cookieValue(r, tokenCookieName) idstr := clientIdString(r) if isValidToken(token, idstr) { httpHandler.ServeHTTP(w, r) } else { // No token, or token is not valid http.Error(w, "Invalid token", http.StatusUnauthorized) } }) }
The RequireAuth function looks at a cookie to see if the user is currently logged in (which means the user has been authenticated). If so, RequireAuth calls the handler it was passed, which in this case is the one created by api.NewHandler. If not, then RequireAuth calls http.Error, which prevents the request from being fulfilled and instead returns an authorization error to the web caller. When the mimsrv client gets this error it displays a login dialog.

The other code I added handles things like login, logout, and cookie renewal and expiration, but all of that code other than RequireAuth is specific to my implementation of authentication. You could instead, for example, use OAuth to authenticate, in which case you would have a completely different mechanism for authenticating a user, but you could still use a function similar to RequireAuth and wire it in the same way.

Adding Authorization

Wrapping selected request paths as described above makes it so that authentication provides authorization for those requests. This coarse-grained authorization is a good start, but for mimsrv I wanted to be able to use fine-grained authorization as well. As this is a simple program with a very small number of users, I don't need anything sophisticated such as role-based authorization. I chose to implement a model in which I only define permissions for global actions, then assign those permissions directly to users.

For this simple permissions model, I needed to be able to define permissions, assign them to users, and check them at run-time before performing an action that requires authorization. My permissions are simple strings, stored in a column in the CSV file that defines my users. To give a permission to a user, I manually edit that CSV file, and to check for authorization before taking an action, the code looks for that permission string in the set of permissions for the current user.

The one piece that is not obvious is how to pass the user's permissions to the code that needs to check them. The reason this is not obvious is because the http routing package defines the function signature for the functions that process an http request, and that function signature includes only the request and a writer for the response. You can't simply add another argument in which you pass your user information, so you have to dig a little deeper to figure out how to pass along that information.

The solution relies on the fact that there is a Context attached to the Request that is passed to the handler function. By adding the user info to the Context, you can then extract that information further along in the processing when you need to check the permission.

The RequireAuth function validates that the user making the request is authenticated, so it already has information about who the user is, and this is the point at which we want to add the user info to the Context. We do this in our RequireAuth function by replacing this line:
httpHandler.ServeHTTP(w, r)
with these lines:
user := userFromToken(token) mimRequest := requestWithContextUser(r, user) httpHandler.ServeHTTP(w, mimRequest) func requestWithContextUser(r *http.Request, user *users.User) *http.Request { mimContext := context.WithValue(r.Context(), ctxUserKey, user) return r.WithContext(mimContext) }
When the code needs to know whether the current user is authorized for an action, it can call the new CurrentUser function, which retrieves the user info from the Context attached to the Request, from which the code can query the user's permissions:
func CurrentUser(r *http.Request) *users.User { v := r.Context().Value(ctxUserKey) if v == nil { return nil } return v.(*users.User) }

Summary

While implementing authentication and authorization in a web server takes more than just a few lines of code, at least the part about how it gets tied in to the http processing in go is only a few lines. Although that part is only a few lines of code, it took me a while to dig around and find exactly how to do that. I hope that this article can save some other people a bit of time when doing their own research on how to add auth to a go web server.