Skip to content

Commit e897970

Browse files
authored
Merge pull request #6 from djedjethai/mongo-v4
Mongo v4
2 parents 64a758f + ba43a9f commit e897970

13 files changed

+1642
-273
lines changed

README.md

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,70 @@ $ go get -u -v gopkg.in/go-oauth2/mongo.v3
1111
## Usage
1212

1313
``` go
14-
package main
15-
16-
import (
17-
"gopkg.in/go-oauth2/mongo.v3"
18-
"gopkg.in/oauth2.v3/manage"
14+
import(
15+
"github.com/go-oauth2/oauth2/v4/manage"
16+
"github.com/go-oauth2/oauth2/v4/server"
17+
mongo "gopkg.in/go-oauth2/mongo.v3"
1918
)
2019

21-
func main() {
22-
manager := manage.NewDefaultManager()
20+
func main(){
21+
manager := manage.NewDefaultManager()
22+
23+
/*
24+
* only for a MongoDB replicaSet deployment
25+
* Using a replicaSet is recommended as it allows for MongoDB's native support for transactions
26+
**/
27+
// mongoConf := mongo.NewConfigReplicaSet(
28+
// "mongodb://localhost:27017,localhost:28017,localhost:29017/?replicaSet=myReplicaSet",
29+
// "oauth2",
30+
// )
31+
32+
// set connectionTimeout(7s) and the requestsTimeout(5s) // is optional
33+
storeConfigs := mongo.NewStoreConfig(7, 5)
34+
35+
/*
36+
* for a single mongoDB node
37+
* if the oauth2 service is deployed with more than one instance
38+
* each mongoConf should have unique serviceName
39+
**/
40+
mongoConf := mongo.NewConfigNonReplicaSet(
41+
"mongodb://127.0.0.1:27017",
42+
"oauth2", // database name
43+
"admin", // username to authenticate with db
44+
"password", // password to authenticate with db
45+
"serviceName",
46+
)
2347

2448
// use mongodb token store
2549
manager.MapTokenStorage(
26-
mongo.NewTokenStore(mongo.NewConfig(
27-
"mongodb://127.0.0.1:27017",
28-
"oauth2",
29-
)),
50+
mongo.NewTokenStore(mongoConf, storeConfigs), // with timeout
51+
// mongo.NewTokenStore(mongoConf), // no timeout
3052
)
31-
// ...
53+
54+
clientStore := mongo.NewClientStore(mongoConf, storeConfigs) // with timeout
55+
// clientStore := mongo.NewClientStore(mongoConf) // no timeout
56+
57+
manager.MapClientStorage(clientStore)
58+
59+
// register a service
60+
clientStore.Create(&models.Client{
61+
ID: idvar,
62+
Secret: secretvar,
63+
Domain: domainvar,
64+
UserID: "frontend",
65+
})
66+
67+
// register a second service
68+
clientStore.Create(&models.Client{
69+
ID: idPreorder,
70+
Secret: secretPreorder,
71+
Domain: domainPreorder,
72+
UserID: "prePost",
73+
})
74+
75+
srv := server.NewServer(server.NewConfig(), manager)
76+
77+
// ...
3278
}
3379
```
3480

client_store.go

Lines changed: 184 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,246 @@
11
package mongo
22

33
import (
4-
"github.com/globalsign/mgo"
5-
"gopkg.in/oauth2.v3"
6-
"gopkg.in/oauth2.v3/models"
4+
"context"
5+
"errors"
6+
"log"
7+
"time"
8+
9+
"github.com/go-oauth2/oauth2/v4"
10+
"github.com/go-oauth2/oauth2/v4/models"
11+
"go.mongodb.org/mongo-driver/bson"
12+
"go.mongodb.org/mongo-driver/mongo"
13+
"go.mongodb.org/mongo-driver/mongo/options"
714
)
815

16+
// TODO
17+
// add retry mecanism on all requests
18+
// if retry fail ping the db
19+
// if ping fail crash the service
20+
// that should be optional as a service's restart mecanism should be implemented
21+
22+
// StoreConfig hold configs common to all Configs(ClientConfig, TokenConfig)
23+
type StoreConfig struct {
24+
db string
25+
service string
26+
connectionTimeout int
27+
requestTimeout int
28+
isReplicaSet bool
29+
}
30+
31+
func NewStoreConfig(ctout, rtout int) *StoreConfig {
32+
return &StoreConfig{
33+
connectionTimeout: ctout,
34+
requestTimeout: rtout,
35+
}
36+
}
37+
38+
func NewDefaultStoreConfig(db, service string, isReplicasSet bool) *StoreConfig {
39+
return &StoreConfig{
40+
db: db,
41+
service: service,
42+
connectionTimeout: 0,
43+
requestTimeout: 0,
44+
isReplicaSet: isReplicasSet,
45+
}
46+
}
47+
48+
// setRequestContext set a WithTimeout or Background context
49+
func (sc *StoreConfig) setRequestContext() (context.Context, context.CancelFunc) {
50+
ctx := context.Background()
51+
if sc.requestTimeout > 0 {
52+
log.Println("Request timeout: ", sc.requestTimeout)
53+
timeout := time.Duration(sc.requestTimeout) * time.Second
54+
return context.WithTimeout(ctx, timeout)
55+
}
56+
return nil, func() {}
57+
}
58+
59+
// setTransactionCreateContext is specific to the transaction(if not a replicaSet)
60+
func (sc *StoreConfig) setTransactionCreateContext() (context.Context, context.CancelFunc) {
61+
ctx := context.Background()
62+
if sc.requestTimeout > 0 {
63+
// at max TransactionCreate run 9 requests
64+
timeout := time.Duration(sc.requestTimeout*9) * time.Second
65+
return context.WithTimeout(ctx, timeout)
66+
}
67+
return nil, func() {}
68+
}
69+
970
// ClientConfig client configuration parameters
1071
type ClientConfig struct {
1172
// store clients data collection name(The default is oauth2_clients)
1273
ClientsCName string
74+
storeConfig *StoreConfig
1375
}
1476

1577
// NewDefaultClientConfig create a default client configuration
16-
func NewDefaultClientConfig() *ClientConfig {
78+
func NewDefaultClientConfig(strCfgs *StoreConfig) *ClientConfig {
1779
return &ClientConfig{
1880
ClientsCName: "oauth2_clients",
81+
storeConfig: strCfgs,
1982
}
2083
}
2184

2285
// NewClientStore create a client store instance based on mongodb
23-
func NewClientStore(cfg *Config, ccfgs ...*ClientConfig) *ClientStore {
24-
session, err := mgo.Dial(cfg.URL)
86+
func NewClientStore(cfg *Config, scfgs ...*StoreConfig) *ClientStore {
87+
clientOptions := options.Client().ApplyURI(cfg.URL)
88+
ctx := context.TODO()
89+
ctxPing := context.TODO()
90+
91+
if len(scfgs) > 0 && scfgs[0].connectionTimeout > 0 {
92+
newCtx, cancel := context.WithTimeout(context.Background(), time.Duration(scfgs[0].connectionTimeout)*time.Second)
93+
ctx = newCtx
94+
defer cancel()
95+
clientOptions.SetConnectTimeout(time.Duration(scfgs[0].connectionTimeout) * time.Second)
96+
}
97+
98+
if len(scfgs) > 0 && scfgs[0].requestTimeout > 0 {
99+
newCtx, cancel := context.WithTimeout(context.Background(), time.Duration(scfgs[0].requestTimeout)*time.Second)
100+
ctxPing = newCtx
101+
defer cancel()
102+
clientOptions.SetConnectTimeout(time.Duration(scfgs[0].requestTimeout) * time.Second)
103+
}
104+
105+
if !cfg.IsReplicaSet {
106+
clientOptions.SetAuth(options.Credential{
107+
Username: cfg.Username,
108+
Password: cfg.Password,
109+
})
110+
}
111+
112+
c, err := mongo.Connect(ctx, clientOptions)
113+
if err != nil {
114+
log.Fatal("ClientStore failed to connect mongo: ", err)
115+
} else {
116+
log.Println("Connection to mongoDB successful")
117+
}
118+
119+
err = c.Ping(ctxPing, nil)
25120
if err != nil {
26-
panic(err)
121+
log.Fatal("MongoDB ping failed:", err)
27122
}
28123

29-
return NewClientStoreWithSession(session, cfg.DB, ccfgs...)
124+
log.Println("Ping db successfull")
125+
126+
return NewClientStoreWithSession(c, cfg, scfgs...)
30127
}
31128

32129
// NewClientStoreWithSession create a client store instance based on mongodb
33-
func NewClientStoreWithSession(session *mgo.Session, dbName string, ccfgs ...*ClientConfig) *ClientStore {
130+
func NewClientStoreWithSession(client *mongo.Client, cfg *Config, scfgs ...*StoreConfig) *ClientStore {
131+
strCfgs := NewDefaultStoreConfig(cfg.DB, cfg.Service, cfg.IsReplicaSet)
132+
34133
cs := &ClientStore{
35-
dbName: dbName,
36-
session: session,
37-
ccfg: NewDefaultClientConfig(),
134+
client: client,
135+
ccfg: NewDefaultClientConfig(strCfgs),
38136
}
39-
if len(ccfgs) > 0 {
40-
cs.ccfg = ccfgs[0]
137+
138+
if len(scfgs) > 0 {
139+
if scfgs[0].connectionTimeout > 0 {
140+
cs.ccfg.storeConfig.connectionTimeout = scfgs[0].connectionTimeout
141+
}
142+
if scfgs[0].requestTimeout > 0 {
143+
cs.ccfg.storeConfig.requestTimeout = scfgs[0].requestTimeout
144+
}
41145
}
42146

43147
return cs
44148
}
45149

46150
// ClientStore MongoDB storage for OAuth 2.0
47151
type ClientStore struct {
48-
ccfg *ClientConfig
49-
dbName string
50-
session *mgo.Session
152+
ccfg *ClientConfig
153+
client *mongo.Client
51154
}
52155

53156
// Close close the mongo session
54157
func (cs *ClientStore) Close() {
55-
cs.session.Close()
158+
if err := cs.client.Disconnect(context.Background()); err != nil {
159+
log.Fatal(err)
160+
}
56161
}
57162

58-
func (cs *ClientStore) c(name string) *mgo.Collection {
59-
return cs.session.DB(cs.dbName).C(name)
163+
func (cs *ClientStore) c(name string) *mongo.Collection {
164+
return cs.client.Database(cs.ccfg.storeConfig.db).Collection(name)
60165
}
61166

62-
func (cs *ClientStore) cHandler(name string, handler func(c *mgo.Collection)) {
63-
session := cs.session.Clone()
64-
defer session.Close()
65-
handler(session.DB(cs.dbName).C(name))
66-
return
67-
}
167+
// Create create client information
168+
func (cs *ClientStore) Create(info oauth2.ClientInfo) (err error) {
169+
ctx := context.Background()
68170

69-
// Set set client information
70-
func (cs *ClientStore) Set(info oauth2.ClientInfo) (err error) {
71-
cs.cHandler(cs.ccfg.ClientsCName, func(c *mgo.Collection) {
72-
entity := &client{
73-
ID: info.GetID(),
74-
Secret: info.GetSecret(),
75-
Domain: info.GetDomain(),
76-
UserID: info.GetUserID(),
77-
}
171+
ctxReq, cancel := cs.ccfg.storeConfig.setRequestContext()
172+
defer cancel()
173+
if ctxReq != nil {
174+
ctx = ctxReq
175+
}
176+
177+
entity := &client{
178+
ID: info.GetID(),
179+
Secret: info.GetSecret(),
180+
Domain: info.GetDomain(),
181+
UserID: info.GetUserID(),
182+
}
183+
184+
collection := cs.c(cs.ccfg.ClientsCName)
78185

79-
if cerr := c.Insert(entity); cerr != nil {
80-
err = cerr
81-
return
186+
_, err = collection.InsertOne(ctx, entity)
187+
if err != nil {
188+
if !mongo.IsDuplicateKeyError(err) {
189+
log.Fatal(err)
190+
} else {
191+
return nil
82192
}
83-
})
193+
}
84194

85195
return
86196
}
87197

88198
// GetByID according to the ID for the client information
89-
func (cs *ClientStore) GetByID(id string) (info oauth2.ClientInfo, err error) {
90-
cs.cHandler(cs.ccfg.ClientsCName, func(c *mgo.Collection) {
91-
entity := new(client)
199+
func (cs *ClientStore) GetByID(ctx context.Context, id string) (info oauth2.ClientInfo, err error) {
200+
ctxReq, cancel := cs.ccfg.storeConfig.setRequestContext()
201+
defer cancel()
202+
if ctxReq != nil {
203+
ctx = ctxReq
204+
}
92205

93-
if cerr := c.FindId(id).One(entity); cerr != nil {
94-
err = cerr
95-
return
206+
filter := bson.M{"_id": id}
207+
result := cs.c(cs.ccfg.ClientsCName).FindOne(ctx, filter)
208+
if err := result.Err(); err != nil {
209+
if err == mongo.ErrNoDocuments {
210+
return nil, err
96211
}
212+
return nil, errors.New("Internal server error, no client found for this ID")
97213

98-
info = &models.Client{
99-
ID: entity.ID,
100-
Secret: entity.Secret,
101-
Domain: entity.Domain,
102-
UserID: entity.UserID,
103-
}
104-
})
214+
}
215+
216+
entity := &client{}
217+
if err := result.Decode(entity); err != nil {
218+
log.Println(err)
219+
}
220+
221+
info = &models.Client{
222+
ID: entity.ID,
223+
Secret: entity.Secret,
224+
Domain: entity.Domain,
225+
UserID: entity.UserID,
226+
}
105227

106228
return
107229
}
108230

109231
// RemoveByID use the client id to delete the client information
110232
func (cs *ClientStore) RemoveByID(id string) (err error) {
111-
cs.cHandler(cs.ccfg.ClientsCName, func(c *mgo.Collection) {
112-
if cerr := c.RemoveId(id); cerr != nil {
113-
err = cerr
114-
return
115-
}
116-
})
233+
ctx := context.Background()
117234

118-
return
235+
ctxReq, cancel := cs.ccfg.storeConfig.setRequestContext()
236+
defer cancel()
237+
if ctxReq != nil {
238+
ctx = ctxReq
239+
}
240+
241+
filter := bson.M{"_id": id}
242+
_, err = cs.c(cs.ccfg.ClientsCName).DeleteOne(ctx, filter)
243+
return err
119244
}
120245

121246
type client struct {

0 commit comments

Comments
 (0)