Skip to content

Commit

Permalink
feat: support display name
Browse files Browse the repository at this point in the history
Endpoints now have a display name which is used to build a proper caller
ID to be used when dialing internal users. This information is also
provided by the bouncer so Stasis apps know which caller ID to use for
calling the other party.

Closes #8.
  • Loading branch information
crazybolillo committed Aug 6, 2024
1 parent 7708f59 commit d4254f9
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 36 deletions.
25 changes: 16 additions & 9 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ definitions:
properties:
allow:
type: boolean
callerid:
type: string
destination:
type: string
type: object
Expand All @@ -21,6 +23,8 @@ definitions:
type: array
context:
type: string
display_name:
type: string
extension:
type: string
id:
Expand All @@ -34,22 +38,24 @@ definitions:
transport:
type: string
type: object
handler.listEndpointsRequest:
properties:
endpoints:
items:
$ref: '#/definitions/sqlc.ListEndpointsRow'
type: array
type: object
sqlc.ListEndpointsRow:
handler.listEndpointEntry:
properties:
context:
type: string
display_name:
type: string
extension:
type: string
id:
type: string
type: object
handler.listEndpointsRequest:
properties:
endpoints:
items:
$ref: '#/definitions/handler.listEndpointEntry'
type: array
type: object
host: localhost:8080
info:
contact: {}
Expand Down Expand Up @@ -79,7 +85,8 @@ paths:
description: Bad Request
"500":
description: Internal Server Error
summary: Determine whether the specified action (call) is allowed or not.
summary: Determine whether the specified action (call) is allowed or not and
provide details on how
tags:
- bouncer
/endpoint:
Expand Down
9 changes: 7 additions & 2 deletions internal/bouncer/bouncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
type Response struct {
Allow bool `json:"allow"`
Destination string `json:"destination"`
CallerID string `json:"callerid"`
}

type Bouncer struct {
Expand All @@ -30,14 +31,18 @@ func (b *Bouncer) Check(ctx context.Context, endpoint, dialed string) Response {
}

queries := sqlc.New(tx)
destination, err := queries.GetEndpointByExtension(ctx, db.Text(dialed))
row, err := queries.GetEndpointByExtension(ctx, sqlc.GetEndpointByExtensionParams{
ID: endpoint,
Extension: db.Text(dialed),
})
if err != nil {
slog.Error("Failed to retrieve endpoint", slog.String("dialed", dialed), slog.String("reason", err.Error()))
return result
}

return Response{
Allow: true,
Destination: destination,
Destination: row.ID,
CallerID: row.Callerid.String,
}
}
3 changes: 2 additions & 1 deletion internal/handler/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ func (e *Authorization) Router() chi.Router {
return r
}

// @Summary Determine whether the specified action (call) is allowed or not.
// @Summary Determine whether the specified action (call) is allowed or not and provide details on how
// to accomplish it.
// @Accept json
// @Produce json
// @Param payload body AuthorizationRequest true "Action to be reviewed"
Expand Down
49 changes: 45 additions & 4 deletions internal/handler/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/crazybolillo/eryth/internal/db"
"github.com/crazybolillo/eryth/internal/sqlc"
"github.com/go-chi/chi/v5"
Expand All @@ -27,10 +28,18 @@ type createEndpointRequest struct {
Codecs []string `json:"codecs"`
MaxContacts int32 `json:"max_contacts,omitempty"`
Extension string `json:"extension,omitempty"`
DisplayName string `json:"display_name"`
}

type listEndpointEntry struct {
ID string `json:"id"`
Extension string `json:"extension"`
Context string `json:"context"`
DisplayName string `json:"display_name"`
}

type listEndpointsRequest struct {
Endpoints []sqlc.ListEndpointsRow `json:"endpoints"`
Endpoints []listEndpointEntry `json:"endpoints"`
}

func (e *Endpoint) Router() chi.Router {
Expand All @@ -42,6 +51,27 @@ func (e *Endpoint) Router() chi.Router {
return r
}

// displayNameFromClid extracts the display name from a Caller ID. It is expected for the Caller ID to be in
// the following format: "Display Name" <username>
// If no display name is found, an empty string is returned.
func displayNameFromClid(callerID string) string {
if callerID == "" {
return ""
}

start := strings.Index(callerID, `"`)
if start != 0 {
return ""
}

end := strings.LastIndex(callerID, `"`)
if end == -1 || end < 1 {
return ""
}

return callerID[1:end]
}

// @Summary List existing endpoints.
// @Param limit query int false "Limit the amount of endpoints returned" default(15)
// @Produce json
Expand All @@ -63,16 +93,26 @@ func (e *Endpoint) list(w http.ResponseWriter, r *http.Request) {
}

queries := sqlc.New(e.Conn)
endpoints, err := queries.ListEndpoints(r.Context(), int32(limit))
rows, err := queries.ListEndpoints(r.Context(), int32(limit))
if err != nil {
slog.Error("Query execution failed", slog.String("path", r.URL.Path), slog.String("msg", err.Error()))
w.WriteHeader(http.StatusInternalServerError)
return
}
if endpoints == nil {
endpoints = []sqlc.ListEndpointsRow{}
if rows == nil {
rows = []sqlc.ListEndpointsRow{}
}

endpoints := make([]listEndpointEntry, len(rows))
for idx := range len(rows) {
row := rows[idx]
endpoints[idx] = listEndpointEntry{
ID: row.ID,
Extension: row.Extension.String,
Context: row.Context.String,
DisplayName: displayNameFromClid(row.Callerid.String),
}
}
response := listEndpointsRequest{
Endpoints: endpoints,
}
Expand Down Expand Up @@ -137,6 +177,7 @@ func (e *Endpoint) create(w http.ResponseWriter, r *http.Request) {
Transport: db.Text(payload.Transport),
Context: db.Text(payload.Context),
Allow: db.Text(strings.Join(payload.Codecs, ",")),
Callerid: db.Text(fmt.Sprintf(`"%s" <%s>`, payload.DisplayName, payload.ID)),
})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
Expand Down
44 changes: 44 additions & 0 deletions internal/handler/endpoint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package handler

import "testing"

func TestDisplayNameFromClid(t *testing.T) {
cases := map[string]struct {
callerID string
displayName string
}{
"valid": {
`"Kiwi Snow" <kiwi99>`,
"Kiwi Snow",
},
"empty": {
"",
"",
},
"single_colon": {
`"`,
"",
},
"empty_quotes": {
`""`,
"",
},
"missing_start_quote": {
`John Smith" <john>`,
"",
},
"missing_end_quote": {
`"John Smith <john>`,
"",
},
}

for name, tt := range cases {
t.Run(name, func(t *testing.T) {
got := displayNameFromClid(tt.callerID)
if got != tt.displayName {
t.Errorf("got %q, want %q", got, tt.displayName)
}
})
}
}
46 changes: 33 additions & 13 deletions internal/sqlc/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 9 additions & 7 deletions queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ VALUES

-- name: NewEndpoint :one
INSERT INTO ps_endpoints
(id, transport, aors, auth, context, disallow, allow)
(id, transport, aors, auth, context, disallow, allow, callerid)
VALUES
($1, $2, $1, $1, $3, 'all', $4)
($1, $2, $1, $1, $3, 'all', $4, $5)
RETURNING sid;

-- name: DeleteEndpoint :exec
Expand All @@ -28,7 +28,7 @@ DELETE FROM ps_auths WHERE id = $1;

-- name: ListEndpoints :many
SELECT
pe.id, pe.context, ee.extension
pe.id, pe.callerid, pe.context, ee.extension
FROM
ps_endpoints pe
LEFT JOIN
Expand All @@ -44,10 +44,12 @@ VALUES

-- name: GetEndpointByExtension :one
SELECT
ps_endpoints.id
dest.id, src.callerid
FROM
ps_endpoints
ps_endpoints dest
INNER JOIN
ery_extension ee on ps_endpoints.sid = ee.endpoint_id
ery_extension ee ON dest.sid = ee.endpoint_id
INNER JOIN
ps_endpoints src ON src.id = $1
WHERE
ee.extension = $1;
ee.extension = $2;

0 comments on commit d4254f9

Please sign in to comment.