From df8a5243eb80bc0009e6c38faf0ced05b844a30a Mon Sep 17 00:00:00 2001
From: ale <ale@incal.net>
Date: Sat, 30 Jun 2018 08:38:38 +0100
Subject: [PATCH] Add an overview of the organization of the code

---
 CONTRIBUTING.md | 177 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 177 insertions(+)
 create mode 100644 CONTRIBUTING.md

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..97d65cb6
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,177 @@
+Code organization and how to add functionality
+===
+
+The codebase is still fairly messy: we just haven't found (yet?) a
+good way to abstract functions and interfaces that minimizes the
+effort of writing business logic.
+
+Anyway, in case you want to add new functionality to the
+accountserver, here's a quick overview of what to do and what to
+consider:
+
+
+## Adding functionality to the backend
+
+The backend interface is in [service.go](service.go) (the *Backend*
+and *TX* interfaces), and has methods to manipulate objects in the
+user database. The data types used by this interface (most importantly
+*User* and *Resource*) are defined in [types.go](types.go) instead.
+
+In some cases, your new functionality may require changes in the data
+model as well. There are two major options available, depending on the
+desired visibility of the new data:
+
+### Adding public data
+
+In this case you need to add *publicly-visible* data to the User or to
+a Resource. This is relatively straightforward as it requires no
+changes to the TX interface.
+
+Suppose you are adding a field called *color* (whatever, doesn't
+matter) to email resources.
+
+* Add the *color* field to the *Email* type in types.go:
+
+```
+type Email struct {
+        ...
+        Color string `json:"color"`
+}
+```
+
+* The LDAP backend implementation needs to know which LDAP attribute
+  this field corresponds to. This is done
+  in [backend/resources.go](backend/resources.go), in the
+  FromLDAP/ToLDAP methods of the *emailResourceHandler* type:
+
+```
+func (h *emailResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Resource, error) {
+        ...
+        return &accountserver.Resource{
+                ...
+                Email: &accountserver.Email{
+                        ...
+                        Color: entry.GetAttributeValue("color"),
+                },
+        }
+}
+
+func (h *emailResourceHandler) ToLDAP(rsrc *accountserver.Resource) []ldap.PartialAttribute {
+	    return []ldap.PartialAttribute{
+                ...
+                {Type: "color", Vals: s2l(rsrc.Email.Color)},
+        }
+}
+```
+
+* Validation code for the new field must be added
+  to [validators.go](validators.go) in the
+  validationContext.validEmailResource function:
+
+```
+func (v *validationContext) validEmailResource() ResourceValidatorFunc {
+        return func(ctx context.Context, r *Resource, user *User) error {
+                ...
+                if r.Email.Color != "blue" {
+                        return errors.New("oh no, the color is wrong")
+                }
+                ...
+        }
+}
+```
+
+* If a newly created email resource should have a default value for
+  the *color* field, it needs to be added to the
+  templateContext.emailResourceTemplate function in the same
+  validators.go file:
+
+```
+func (c *templateContext) emailResourceTemplate(ctx context.Context, r *Resource, user *User) {
+        ...
+        r.Email.Color = "blue"
+        ...
+}
+```
+
+### Adding private data
+
+When adding new private data, more complex changes are required: private
+data should not be stored in the public data types, but one should use
+dedicated methods on the TX interface instead.
+
+Suppose for instance you want to add a new authentication mechanism,
+*third-factor authentication* or *3FA*. The authentication handler
+(which is not part of accountserver!) requires a secret *magic word*
+to authenticate a user, so we need a dedicated TX method to set it. We
+would also like to be able to tell whether a user has 3FA enabled or
+not (it's nice to show it in the user management panel, for instance),
+so we are going to add a boolean *Has3FA* field to the *User* type
+too.
+
+This might result in something like:
+
+```
+type TX interface {
+        ...
+        SetUser3FAMagicWord(context.Context, *User, string) error
+}
+```
+
+which would then need to be implemented by the LDAP backend.
+
+
+## Adding functionality to the API
+
+Once the backend changes, where necessary, have been made, you may
+want to add a public method to the API, to allow clients to manipulate
+the new data. The public API is defined by the *AccountService*
+type. This type is defined in [service.go](service.go), which contains
+basic private functionality, but all "business logic" methods are
+actually in [actions.go](actions.go). The API is then exported as an
+HTTP service by [server/server.go](server/server.go).
+
+As an example, let's add a new *SetEmailColor* API method to set the
+*color* attribute of email resources from an earlier example
+above. We're going to need a request type, but the action returns no
+data so we can avoid defining a response type.
+
+There's a bit of boilerplate involved. In brief, this is going to be a
+*resource*-level method (since it operates on a specific resource, not
+on the user itself), and we're going to allow users to set their own
+"email color".
+
+So, in actions.go:
+
+```
+type SetEmailColorRequest struct {
+        ResourceRequestBase
+        Color string `json:"color"`
+}
+
+func (s *AccountService) SetEmailColor(ctx context.Context, tx TX, req *SetEmailColorRequest) error {
+        return s.handleResourceRequest(ctx, tx, req, s.authResource(req.ResourceRequestBase), func(ctx context.Context, r *Resource) error {
+                r.Email.Color = req.Color
+                return tx.UpdateResource(ctx, r)
+        })
+}
+```
+
+In reality we will want to do more things, like validating the *color*
+field in the incoming request.
+
+Finally, the new API method must be exposed on the HTTP service. In
+server/server.go we should create a new handler wrapper function:
+
+```
+func (s *AccountServer) handleSetEmailColor(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
+        var req as.SetEmailColorRequest
+        return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
+                return s.service.SetEmailColor(ctx, tx, &req)
+        })
+}
+
+func (s *AccountServer) Handler() http.Handler {
+        ...
+        h.HandleFunc("/api/resource/email/set_color", s.withTx(s.handleSetEmailColor))
+}
+```
-- 
GitLab