Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
10
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Open sidebar
ai3
accountserver
Commits
df8a5243
Commit
df8a5243
authored
Jun 30, 2018
by
ale
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add an overview of the organization of the code
parent
96cf6388
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
177 additions
and
0 deletions
+177
-0
CONTRIBUTING.md
CONTRIBUTING.md
+177
-0
No files found.
CONTRIBUTING.md
0 → 100644
View file @
df8a5243
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))
}
```
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment