Created
January 21, 2013 03:26
-
-
Save iansmith/4583407 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"net/http" | |
"strings" | |
"seven5/auth" | |
"seven5/user" | |
"seven5" | |
"code.google.com/p/goauth2/oauth" | |
) | |
const ( | |
NAME = "myauth" | |
SCOPE = "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email " | |
LOGIN_TYPE = "auto" //can be "force" or "auto" | |
) | |
//MyUserWire defines what passes back and forth with the client side. This must be ok for Seven5 to | |
//serialize to json and must include an Id field of type seven5.Id. Use only seven5 types to make | |
//sure it flattens correctly. | |
type MyUserWire struct { | |
Id seven5.Id | |
ExternalId seven5.String255 | |
Name seven5.String255 | |
Email seven5.String255 | |
Pic seven5.String255 | |
} | |
//MyUser is both a seven5.Session and a user.Basic. Under the covers it uses a google user retreived | |
//at login to know the user's name, email, etc. Deleted objects are never really removed, just | |
//marked. | |
type MyUser struct { | |
*seven5.SimpleSession | |
*auth.GoogleUser | |
Id seven5.Id | |
sup *MySupport | |
deleted bool | |
} | |
//ToWire converts the user internal structure to the wire structure for sending to the other side. This | |
//cannot be done automatically because seven5 doesn't know what is in MyUser that you want marshalled. | |
//This function is required by the interface user.Basic | |
func (self *MyUser) ToWire() interface{} { | |
return &MyUserWire{ | |
Id: self.Id, | |
ExternalId: seven5.String255(self.GoogleId), | |
Name: seven5.String255(self.Name), | |
Email: seven5.String255(self.Email()), | |
Pic: seven5.String255(self.Picture), | |
} | |
} | |
//Id is required by the contract of user.Basic. This is so it can do simple comparisons | |
//(x.WireId()==y.WireId()) without knowing the true type. | |
func (self *MyUser) WireId() seven5.Id { | |
return self.Id | |
} | |
//MySupport is a struct that maintains a list of all known users in memory. It also uses the environment | |
//to know the "magic" google ids that are admins. THIS IMPLEMENTATION IS NOT CONCURRENT SAFE! It's | |
//just a sample to show the API. | |
type MySupport struct { | |
env *auth.EnvironmentVars | |
conn auth.ServiceConnector | |
all []*MyUser | |
next seven5.Id | |
} | |
//IsStaff returns true if the user is a staff member. Staff members are intended to be lower than | |
//admins but we just map staff to admins. | |
func (self *MySupport) IsStaff(b user.Basic) bool { | |
return self.IsAdmin(b) | |
} | |
//IsAdmin returns true if the environment contains an environment variable of the form | |
//"MYAUTH_ADMIN" that has the email of this user contained somewhere in it. | |
func (self *MySupport) IsAdmin(b user.Basic) bool { | |
v:=self.env.GetAppValue("ADMIN") | |
return strings.Index(v,b.Email())!=-1 | |
} | |
//FindByEmail computes the seven5 Id from the basic user object. It does this by walking the entire | |
//list of known users, looking for the email address. It panics if it can't find the user sought. | |
func (self *MySupport) FindByEmail(cand user.Basic) seven5.Id { | |
for _,v:=range self.KnownUsers() { | |
if v.Email()==cand.Email() { | |
return v.WireId() | |
} | |
} | |
panic("couldn't find user to convert to id!") | |
} | |
//FindById searches for user with a known Id. | |
func (self *MySupport) FindById(id seven5.Id) user.Basic { | |
for _,v:=range self.KnownUsers() { | |
if v.WireId()==id { | |
return v | |
} | |
} | |
panic("couldn't find user from an id id!") | |
} | |
//Update is called by the resource implementation to allow the proposed (wire) object to | |
//have some or all of it's fields copied into existing. Obviously, the fields need to be | |
//sanity checked. | |
func (self *MySupport) UpdateFields(p interface{}, e user.Basic) { | |
proposed:=p.(*MyUserWire) | |
existing:=e.(*MyUser) | |
email:=strings.TrimSpace(string(proposed.Email)) | |
name:=strings.TrimSpace(string(proposed.Name)) | |
pic:=strings.TrimSpace(string(proposed.Pic)) | |
//we store these values inside the google object | |
if email!=existing.Email() && email!="" { | |
existing.SetEmail(email) | |
} | |
if name!=existing.Name && name!="" { | |
existing.GoogleUser.Name=name | |
} | |
if pic!=existing.Picture && pic!="" { | |
existing.GoogleUser.Picture=pic | |
} | |
//not allowed to change any other fields! | |
} | |
//KnownUsers returns a copy of all known users that are not deleted. | |
func (self *MySupport) KnownUsers() []user.Basic { | |
result:=[]user.Basic{} | |
for _, u:=range self.all { | |
if u.deleted { | |
continue | |
} | |
result = append(result, u) | |
} | |
return result | |
} | |
func (self *MySupport) Delete(id seven5.Id) user.Basic { | |
b:=self.FindById(id) | |
u:=b.(*MyUser) | |
u.deleted=true | |
return u | |
} | |
//NewMySupport creates an instance of MySupport. Should only be one in the program. You have to | |
//supply these parameters because we need them when we generate a new session. | |
func NewMySupport(env *auth.EnvironmentVars, conn auth.ServiceConnector) *MySupport{ | |
return &MySupport{env, conn, nil, 0} | |
} | |
//Generate is called by the basic infrastructure to return a new "session" which is also a user. | |
//This implementation does a call to the fetcher, other values are ignored. This is where the | |
//"magic" happens to take a google user and create one of our MyUser object from it. | |
func (self *MySupport) Generate(t *oauth.Transport) (seven5.Session, error) { | |
i, err:=self.conn.Fetch(t) | |
if err!=nil { | |
return nil, err | |
} | |
google:=i.(*auth.GoogleUser) | |
//create the _internal_ object | |
u:=&MyUser { | |
Id: self.next, | |
SimpleSession:seven5.NewSimpleSession(), | |
GoogleUser:google, | |
sup:self, | |
deleted: false, | |
} | |
//update the counter | |
self.next++ | |
//keep track of all known users | |
self.all = append(self.all, u) | |
return u,nil | |
} | |
func main() { | |
//we use the environment variables and the heroku name for our app deployment URL. | |
//Environment var PORT controls the port number we run on, both for production and test. | |
heroku := auth.NewHerokuDeploy(NAME) | |
//google connector needs the environment for client id and secret and heroku for URL calculating | |
goog := auth.NewGoogleAuthConnector(SCOPE, LOGIN_TYPE, heroku.Environment(), heroku) | |
//hook our application type to the Basic User support | |
mgr:=user.NewBasicManager(NewMySupport(heroku.Environment(), goog)) | |
//a dispatcher takes in raw requests and picks the appropriate API to dispatch them on | |
//base dispatcher works with rest resources | |
bd := seven5.NewBaseDispatcher(NAME, mgr) | |
//seven5.ServeMux is a layer on top of the standard http.ServeMux and is downward compatible | |
mux := seven5.NewServeMux(nil) | |
//maps a user resource at "user" with wire type from above and implementation provided by the | |
//basic user support. users are accessed at /rest/user | |
bd.Resource("user", &MyUserWire{}, mgr.UserResource()) | |
//maps the "meta" user resource into the url space at /rest/usermeta | |
//bd.Resource("usermeta", userManager.MetaWire(), userManager.MetaImplementation()) | |
//we create an Auth Dispatcher, and use the defaults to map it to /auth/PROVIDERNAME/... in the | |
//URL space | |
auth := seven5.AuthDispatcherFromBase(bd, mux) | |
//we use google for login and user data | |
auth.AddProvider(goog) | |
//everything is set, just run the normal listen and route loop | |
http.ListenAndServe(fmt.Sprintf(":%d",heroku.Port()), mux) | |
//doesn't return | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment