// Get a list of all members which are currently in the trash. func (m *MembershipDB) EnumerateTrashedMembers(prev string, num int32) ([]*MemberWithKey, error) { var cp *cassandra.ColumnParent = cassandra.NewColumnParent() var pred *cassandra.SlicePredicate = cassandra.NewSlicePredicate() var r *cassandra.KeyRange = cassandra.NewKeyRange() var kss []*cassandra.KeySlice var ks *cassandra.KeySlice var rv []*MemberWithKey var err error // Fetch the protobuf column of the application column family. cp.ColumnFamily = "membership_archive" pred.ColumnNames = [][]byte{ []byte("pb_data"), } if len(prev) > 0 { var uuid cassandra.UUID if uuid, err = cassandra.ParseUUID(prev); err != nil { return rv, err } r.StartKey = append([]byte(archivePrefix), []byte(uuid)...) } else { r.StartKey = []byte(archivePrefix) } r.EndKey = []byte(archiveEnd) r.Count = num kss, err = m.conn.GetRangeSlices(cp, pred, r, cassandra.ConsistencyLevel_ONE) if err != nil { return rv, err } for _, ks = range kss { var member *MemberWithKey var scol *cassandra.ColumnOrSuperColumn var uuid cassandra.UUID = cassandra.UUIDFromBytes( ks.Key[len(archivePrefix):]) if len(ks.Columns) == 0 { continue } for _, scol = range ks.Columns { var col *cassandra.Column = scol.Column if string(col.Name) == "pb_data" { var agreement = new(MembershipAgreement) member = new(MemberWithKey) err = proto.Unmarshal(col.Value, agreement) proto.Merge(&member.Member, agreement.GetMemberData()) member.Key = uuid.String() } } if member != nil { rv = append(rv, member) } } return rv, nil }
// Move the record of the given applicant to a different column family. func (m *MembershipDB) moveRecordToTable( id, initiator, src_table, src_prefix, dst_table, dst_prefix string, ttl int32) error { var uuid cassandra.UUID var bmods map[string]map[string][]*cassandra.Mutation var now time.Time = time.Now() var member *MembershipAgreement var mutation *cassandra.Mutation = cassandra.NewMutation() var value []byte var timestamp int64 var err error uuid, err = cassandra.ParseUUID(id) if err != nil { return err } // First, retrieve the desired membership data. member, timestamp, err = m.GetMembershipRequest(id, src_table, src_prefix) if err != nil { return err } if dst_table == "membership_queue" && len(member.AgreementPdf) == 0 { return errors.New("No membership agreement scan has been uploaded") } // Fill in details concerning the approval. member.Metadata.ApproverUid = proto.String(initiator) member.Metadata.ApprovalTimestamp = proto.Uint64(uint64(now.Unix())) bmods = make(map[string]map[string][]*cassandra.Mutation) bmods[dst_prefix+string(uuid)] = make(map[string][]*cassandra.Mutation) bmods[dst_prefix+string(uuid)][dst_table] = make([]*cassandra.Mutation, 0) bmods[src_prefix+string(uuid)] = make(map[string][]*cassandra.Mutation) bmods[src_prefix+string(uuid)][src_table] = make([]*cassandra.Mutation, 0) value, err = proto.Marshal(member) if err != nil { return err } // Add the application protobuf to the membership data. bmods[dst_prefix+string(uuid)][dst_table] = append( bmods[dst_prefix+string(uuid)][dst_table], newCassandraMutationBytes("pb_data", value, &now, ttl)) // Delete the application data. mutation.Deletion = cassandra.NewDeletion() mutation.Deletion.Predicate = cassandra.NewSlicePredicate() mutation.Deletion.Predicate.ColumnNames = allColumns mutation.Deletion.Timestamp = ×tamp bmods[src_prefix+string(uuid)][src_table] = append( bmods[src_prefix+string(uuid)][src_table], mutation) return m.conn.AtomicBatchMutate(bmods, cassandra.ConsistencyLevel_QUORUM) }
// Retrieve a specific members detailed membership data, but fetch it by the // user name of the member. func (m *MembershipDB) GetMemberDetailByUsername(username string) ( *MembershipAgreement, error) { var member *MembershipAgreement = new(MembershipAgreement) var cp *cassandra.ColumnParent = cassandra.NewColumnParent() var pred *cassandra.SlicePredicate = cassandra.NewSlicePredicate() var kr *cassandra.KeyRange = cassandra.NewKeyRange() var expr *cassandra.IndexExpression = cassandra.NewIndexExpression() var r []*cassandra.KeySlice var ks *cassandra.KeySlice var err error expr.ColumnName = []byte("username") expr.Op = cassandra.IndexOperator_EQ expr.Value = []byte(username) cp.ColumnFamily = "members" pred.ColumnNames = [][]byte{[]byte("pb_data")} kr.StartKey = []byte(memberPrefix) kr.EndKey = []byte(memberEnd) kr.RowFilter = []*cassandra.IndexExpression{expr} r, err = m.conn.GetRangeSlices( cp, pred, kr, cassandra.ConsistencyLevel_ONE) if err != nil { return nil, err } for _, ks = range r { var cos *cassandra.ColumnOrSuperColumn for _, cos = range ks.Columns { var col = cos.Column if string(col.Name) == "pb_data" { member = new(MembershipAgreement) err = proto.Unmarshal(col.Value, member) return member, nil } else { return nil, errors.New("Unexpected column " + string(col.Name)) } } } return nil, errors.New("Not found") }
func (self *ProductSearchAPI) ServeHTTP(w http.ResponseWriter, req *http.Request) { var err error var query string = req.FormValue("q") var rawdata []byte var res CategorizedSearchResult numRequests.Add(1) numAPIRequests.Add(1) // Check the user is in the reqeuested scope. if !self.authenticator.IsAuthenticatedScope(req, self.scope) { numDisallowedScope.Add(1) http.Error(w, "You are not in the right group to access this resource", http.StatusForbidden) return } if len(query) >= 3 { var product_results [][]byte var cp *cassandra.ColumnParent = cassandra.NewColumnParent() var pred *cassandra.SlicePredicate = cassandra.NewSlicePredicate() var kr *cassandra.KeyRange = cassandra.NewKeyRange() var colmap map[string][]*cassandra.ColumnOrSuperColumn var slices []*cassandra.KeySlice var ire *cassandra.InvalidRequestException var ue *cassandra.UnavailableException var te *cassandra.TimedOutException var endkey []byte = []byte(query) var r *SearchResult var pos int = len(endkey) - 1 // Handle overflows for endkey[pos] == 255 && pos > 0 { endkey[pos] = 0 pos-- } // Produce endkey by incrementing the last byte of the start key. endkey[pos]++ // Search for projects with the given name. cp.ColumnFamily = "products_byname" pred.ColumnNames = [][]byte{[]byte("product")} kr.StartKey = []byte(query) kr.EndKey = endkey kr.Count = 32 // Limit to 32 results. slices, ire, ue, te, err = self.client.GetRangeSlices( cp, pred, kr, cassandra.ConsistencyLevel_ONE) if ire != nil { log.Print("Error fetching products_byname: ", ire.Why) numCassandraErrors.Add("invalid-request", 1) http.Error(w, ire.Why, http.StatusInternalServerError) return } if ue != nil { log.Print("Cassandra unavailable when fetching products_byname") numCassandraErrors.Add("invalid-request", 1) http.Error(w, "Database unavailable", http.StatusInternalServerError) return } if te != nil { log.Print("Cassandra timed out when fetching products_byname") numCassandraErrors.Add("timeout", 1) http.Error(w, "Database timed out", http.StatusInternalServerError) return } if err != nil { log.Print("OS error when fetching products_byname: ", err) numCassandraErrors.Add("generic-error", 1) http.Error(w, "OS error talking to database", http.StatusInternalServerError) return } for _, slice := range slices { for _, csc := range slice.Columns { var col *cassandra.Column = csc.Column if col == nil || !col.IsSetValue() { continue } if string(col.Name) != "product" { log.Print("Bizarre products_byname row ", string(slice.Key), " (", slice.Key, "), has ", string(col.Name), " (", col.Name, ")") continue } r = new(SearchResult) r.Name = string(slice.Key) r.Uuid = UUID(col.Value).String() res.Products = append(res.Products, r) } } // Search for projects with the given name. cp.ColumnFamily = "products_bybarcode" pred.ColumnNames = [][]byte{[]byte("product")} kr.StartKey = []byte(query) kr.EndKey = endkey kr.Count = 32 // Limit to 32 results. slices, ire, ue, te, err = self.client.GetRangeSlices( cp, pred, kr, cassandra.ConsistencyLevel_ONE) if ire != nil { log.Print("Error fetching products_bybarcode: ", ire.Why) numCassandraErrors.Add("invalid-request", 1) http.Error(w, ire.Why, http.StatusInternalServerError) return } if ue != nil { log.Print("Cassandra unavailable when fetching products_bybarcode") numCassandraErrors.Add("invalid-request", 1) http.Error(w, "Database unavailable", http.StatusInternalServerError) return } if te != nil { log.Print("Cassandra timed out when fetching products_bybarcode") numCassandraErrors.Add("timeout", 1) http.Error(w, "Database timed out", http.StatusInternalServerError) return } if err != nil { log.Print("OS error when fetching products_bybarcode: ", err) numCassandraErrors.Add("generic-error", 1) http.Error(w, "OS error talking to database", http.StatusInternalServerError) return } for _, slice := range slices { for _, csc := range slice.Columns { var col *cassandra.Column = csc.Column if col == nil || !col.IsSetValue() { continue } if string(col.Name) != "product" { log.Print("Bizarre products_bybarcode row ", string(slice.Key), " (", slice.Key, "), has ", string(col.Name), " (", col.Name, ")") continue } product_results = append(product_results, col.Value) } } // Now, fetch all the product names for products we spotted above. if len(product_results) > 0 { cp.ColumnFamily = "products" pred.ColumnNames = [][]byte{[]byte("name")} colmap, ire, ue, te, err = self.client.MultigetSlice( product_results, cp, pred, cassandra.ConsistencyLevel_ONE) if ire != nil { log.Print("Error fetching products: ", ire.Why) numCassandraErrors.Add("invalid-request", 1) http.Error(w, ire.Why, http.StatusInternalServerError) return } if ue != nil { log.Print("Cassandra unavailable when fetching products") numCassandraErrors.Add("invalid-request", 1) http.Error(w, "Database unavailable", http.StatusInternalServerError) return } if te != nil { log.Print("Cassandra timed out when fetching products") numCassandraErrors.Add("timeout", 1) http.Error(w, "Database timed out", http.StatusInternalServerError) return } if err != nil { log.Print("OS error when fetching products: ", err) numCassandraErrors.Add("generic-error", 1) http.Error(w, "OS error talking to database", http.StatusInternalServerError) return } for key, cscv := range colmap { for _, csc := range cscv { var col *cassandra.Column = csc.Column if col == nil || !col.IsSetValue() { continue } if string(col.Name) != "name" { log.Print("Cassandra returned additional column ", string(col.Name), " (", col.Name, ") for row ", key, " (", []byte(key), ")") continue } r = new(SearchResult) r.Name = string(col.Value) r.Uuid = UUID([]byte(key)).String() res.Products = append(res.Products, r) } } } // TODO(tonnerre): stub // r = new(SearchResult) // r.Name = "ACME Inc." // r.Uuid = "/vendor/acme" // res.Vendors = append(res.Vendors, r) // r = new(SearchResult) // r.Name = "Starship Factory" // r.Uuid = "/vendor/starshipfactory" // res.Vendors = append(res.Vendors, r) // r = new(SearchResult) // r.Name = "RaumZeitLabor e.V." // r.Uuid = "/vendor/rzl" // res.Vendors = append(res.Vendors, r) // r = new(SearchResult) // r.Name = "Doctor in the TARDIS" // r.Uuid = "/vendor/doctor" // res.Vendors = append(res.Vendors, r) } rawdata, err = json.Marshal(res) if err != nil { log.Print("Error marshalling JSON: ", err) numJSONMarshalErrors.Add(1) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Add("Content-type", "application/json") w.WriteHeader(http.StatusOK) _, err = w.Write(rawdata) if err != nil { log.Print("Error writing JSON response: ", err) numHTTPWriteErrors.Add(err.Error(), 1) } }
func (self *ProductViewAPI) ServeHTTP(w http.ResponseWriter, req *http.Request) { var cp *cassandra.ColumnParent var pred *cassandra.SlicePredicate var res []*cassandra.ColumnOrSuperColumn var csc *cassandra.ColumnOrSuperColumn var ire *cassandra.InvalidRequestException var ue *cassandra.UnavailableException var te *cassandra.TimedOutException var prod Product var err error var uuidstr string = req.FormValue("id") var ts int64 = 0 var uuid UUID var rawdata []byte numRequests.Add(1) numAPIRequests.Add(1) // Check the user is in the reqeuested scope. if !self.authenticator.IsAuthenticatedScope(req, self.scope) { numDisallowedScope.Add(1) http.Error(w, "You are not in the right group to access this resource", http.StatusForbidden) return } if len(uuidstr) <= 0 { http.Error(w, "Requested UUID empty", http.StatusNotAcceptable) return } uuid, err = ParseUUID(uuidstr) if err != nil { http.Error(w, "Requested UUID invalid", http.StatusNotAcceptable) return } cp = cassandra.NewColumnParent() cp.ColumnFamily = "products" pred = cassandra.NewSlicePredicate() pred.ColumnNames = [][]byte{ []byte("name"), []byte("price"), []byte("vendor"), []byte("barcodes"), []byte("stock"), } res, ire, ue, te, err = self.client.GetSlice([]byte(uuid), cp, pred, cassandra.ConsistencyLevel_ONE) if ire != nil { log.Print("Invalid request: ", ire.Why) productViewErrors.Add(ire.Why, 1) return } if ue != nil { log.Print("Unavailable") productViewErrors.Add("unavailable", 1) return } if te != nil { log.Print("Request to database backend timed out") productViewErrors.Add("timeout", 1) return } if err != nil { log.Print("Generic error: ", err) productViewErrors.Add(err.Error(), 1) return } for _, csc = range res { var col = csc.Column var cname string if !csc.IsSetColumn() { continue } cname = string(col.Name) if col.IsSetTimestamp() && col.Timestamp > ts { ts = col.Timestamp } if cname == "name" { prod.Name = string(col.Value) } else if cname == "price" { var buf *bytes.Buffer = bytes.NewBuffer(col.Value) err = binary.Read(buf, binary.BigEndian, &prod.Price) if err != nil { log.Print("Row ", uuid.String(), " price is invalid") productViewErrors.Add("corrupted-price", 1) } } else if cname == "vendor" { prod.VendorId = UUID(col.Value).String() } else if cname == "barcodes" { var bc Barcodes err = proto.Unmarshal(col.Value, &bc) if err != nil { log.Print("Row ", uuid.String(), " barcode is invalid") productViewErrors.Add("corrupted-barcode", 1) return } for _, code := range bc.Barcode { prod.Barcodes = append(prod.Barcodes, code) } } else if cname == "stock" { var buf *bytes.Buffer = bytes.NewBuffer(col.Value) err = binary.Read(buf, binary.BigEndian, &prod.Stock) if err != nil { log.Print("Row ", uuid.String(), " stock is invalid") productViewErrors.Add("corrupted-stock", 1) } } } rawdata, err = json.Marshal(prod) if err != nil { log.Print("Error marshalling JSON: ", err) numJSONMarshalErrors.Add(1) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Add("Content-type", "application/json") w.WriteHeader(http.StatusOK) _, err = w.Write(rawdata) if err != nil { log.Print("Error writing JSON response: ", err) numHTTPWriteErrors.Add(err.Error(), 1) } }
func main() { var cf string = "members" var config_file string var config_contents []byte var config membersys.MemberCreatorConfig var greatestUid uint64 = 1000 var now time.Time var noop, verbose bool var welcome *membersys.WelcomeMail var ld *ldap.Conn var sreq *ldap.SearchRequest var lres *ldap.SearchResult var entry *ldap.Entry var tlsconfig tls.Config var mmap map[string]map[string][]*cassandra.Mutation var db *cassandra.RetryCassandraClient var cp *cassandra.ColumnParent var pred *cassandra.SlicePredicate var kr *cassandra.KeyRange var kss []*cassandra.KeySlice var ks *cassandra.KeySlice var err error flag.StringVar(&config_file, "config", "", "Path to the member creator configuration file") flag.BoolVar(&noop, "dry-run", false, "Do a dry run") flag.BoolVar(&verbose, "verbose", false, "Whether or not to display verbose messages") flag.Parse() if len(config_file) == 0 { flag.Usage() return } config_contents, err = ioutil.ReadFile(config_file) if err != nil { log.Fatal("Unable to read ", config_file, ": ", err) } err = proto.Unmarshal(config_contents, &config) if err != nil { err = proto.UnmarshalText(string(config_contents), &config) } if err != nil { log.Fatal("Unable to parse ", config_file, ": ", err) } if config.WelcomeMailConfig != nil { welcome, err = membersys.NewWelcomeMail( config.WelcomeMailConfig) if err != nil { log.Fatal("Error creating WelcomeMail: ", err) } } tlsconfig.MinVersion = tls.VersionTLS12 tlsconfig.ServerName, _, err = net.SplitHostPort( config.LdapConfig.GetServer()) if err != nil { log.Fatal("Can't split ", config.LdapConfig.GetServer(), " into host and port: ", err) } if config.LdapConfig.CaCertificate != nil { var certData []byte certData, err = ioutil.ReadFile(config.LdapConfig.GetCaCertificate()) if err != nil { log.Fatal("Unable to read certificate from ", config.LdapConfig.GetCaCertificate(), ": ", err) } tlsconfig.RootCAs = x509.NewCertPool() tlsconfig.RootCAs.AppendCertsFromPEM(certData) } now = time.Now() if !noop { ld, err = ldap.DialTLS("tcp", config.LdapConfig.GetServer(), &tlsconfig) if err != nil { log.Fatal("Error connecting to LDAP server ", config.LdapConfig.GetServer(), ": ", err) } err = ld.Bind(config.LdapConfig.GetSuperUser()+","+ config.LdapConfig.GetBase(), config.LdapConfig.GetSuperPassword()) if err != nil { log.Fatal("Unable to bind as ", config.LdapConfig.GetSuperUser()+ ","+config.LdapConfig.GetBase(), " to ", config.LdapConfig.GetServer(), ": ", err) } defer ld.Close() sreq = ldap.NewSearchRequest( config.LdapConfig.GetBase(), ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1000, 90, false, "(objectClass=posixAccount)", []string{"uidNumber"}, []ldap.Control{}) // Find the highest assigned UID. lres, err = ld.Search(sreq) if err != nil { log.Fatal("Unable to search for posix accounts in ", config.LdapConfig.GetBase(), ": ", err) } for _, entry = range lres.Entries { var uid string for _, uid = range entry.GetAttributeValues("uidNumber") { var uidNumber uint64 uidNumber, err = strconv.ParseUint(uid, 10, 64) if err != nil { log.Print("Error parsing \"", uid, "\" as a number: ", err) } else if uidNumber > greatestUid { greatestUid = uidNumber } } } } // Connect to Cassandra so we can get a list of records to be processed. db, err = cassandra.NewRetryCassandraClient( config.DatabaseConfig.GetDatabaseServer()) if err != nil { log.Fatal("Error connecting to Cassandra database at ", config.DatabaseConfig.GetDatabaseServer(), ": ", err) } err = db.SetKeyspace(config.DatabaseConfig.GetDatabaseName()) if err != nil { log.Fatal("Error setting keyspace: ", err) } cp = cassandra.NewColumnParent() cp.ColumnFamily = "membership_queue" pred = cassandra.NewSlicePredicate() pred.ColumnNames = [][]byte{[]byte("pb_data")} kr = cassandra.NewKeyRange() kr.StartKey = []byte("queue:") kr.EndKey = []byte("queue;") kss, err = db.GetRangeSlices(cp, pred, kr, cassandra.ConsistencyLevel_QUORUM) if err != nil { log.Fatal("Error getting range slice: ", err) } for _, ks = range kss { mmap = make(map[string]map[string][]*cassandra.Mutation) var csc *cassandra.ColumnOrSuperColumn for _, csc = range ks.Columns { var col *cassandra.Column = csc.Column var agreement membersys.MembershipAgreement var m *cassandra.Mutation if col == nil { continue } if string(col.Name) != "pb_data" { log.Print("Column selected was not as requested: ", col.Name) continue } err = proto.Unmarshal(col.Value, &agreement) if err != nil { log.Print("Unable to parse column ", col.Name, " of ", ks.Key, ": ", err) continue } if agreement.MemberData.Username != nil { var attrs *ldap.AddRequest greatestUid++ attrs = ldap.NewAddRequest("uid=" + asciiFilter(agreement.MemberData.GetUsername()) + "," + config.LdapConfig.GetNewUserSuffix() + "," + config.LdapConfig.GetBase()) attrs.Attribute("uidNumber", []string{ strconv.FormatUint(greatestUid, 10)}) attrs.Attribute("gecos", []string{ asciiFilter(agreement.MemberData.GetName())}) attrs.Attribute("shadowLastChange", []string{"11457"}) attrs.Attribute("shadowMax", []string{"9999"}) attrs.Attribute("shadowWarning", []string{"7"}) attrs.Attribute("gidNumber", []string{strconv.FormatUint( uint64(config.LdapConfig.GetNewUserGid()), 10)}) attrs.Attribute("objectClass", []string{ "account", "posixAccount", "shadowAccount", "top", }) attrs.Attribute("uid", []string{ asciiFilter(agreement.MemberData.GetUsername())}) attrs.Attribute("cn", []string{ asciiFilter(agreement.MemberData.GetUsername())}) attrs.Attribute("homeDirectory", []string{"/home/" + asciiFilter(agreement.MemberData.GetUsername())}) attrs.Attribute("loginShell", []string{ config.LdapConfig.GetNewUserShell()}) attrs.Attribute("userPassword", []string{ agreement.MemberData.GetPwhash(), }) agreement.MemberData.Id = proto.Uint64(greatestUid) if verbose { log.Print("Creating user: uid=" + agreement.MemberData.GetUsername() + "," + config.LdapConfig.GetNewUserSuffix() + "," + config.LdapConfig.GetBase()) } if !noop { var group string err = ld.Add(attrs) if err != nil { log.Print("Unable to create LDAP Account ", agreement.MemberData.GetUsername(), ": ", err) continue } for _, group = range config.LdapConfig.GetNewUserGroup() { var grpadd = ldap.NewModifyRequest("cn=" + group + ",ou=Groups," + config.LdapConfig.GetBase()) grpadd.Add("memberUid", []string{ agreement.MemberData.GetUsername()}) err = ld.Modify(grpadd) if err != nil { log.Print("Unable to add user ", agreement.MemberData.GetUsername(), " to group ", group, ": ", err) } } } } col.Value, err = proto.Marshal(&agreement) if err != nil { log.Print("Error marshalling agreement: ", err) continue } mmap["member:"+string(agreement.MemberData.GetEmail())] = make(map[string][]*cassandra.Mutation) mmap["member:"+string(agreement.MemberData.GetEmail())][cf] = make([]*cassandra.Mutation, 0) makeMutation(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "pb_data", col.Value, now) makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "name", agreement.MemberData.GetName(), now) makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "street", agreement.MemberData.GetStreet(), now) makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "city", agreement.MemberData.GetCity(), now) makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "country", agreement.MemberData.GetCountry(), now) makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "email", agreement.MemberData.GetEmail(), now) if agreement.MemberData.Phone != nil { makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "phone", agreement.MemberData.GetPhone(), now) } if agreement.MemberData.Username != nil { makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "username", agreement.MemberData.GetUsername(), now) } makeMutationLong(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "fee", agreement.MemberData.GetFee(), now) makeMutationBool(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "fee_yearly", agreement.MemberData.GetFeeYearly(), now) makeMutationLong(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "approval_ts", agreement.Metadata.GetApprovalTimestamp(), now) makeMutation(mmap["member:"+string(agreement.MemberData.GetEmail())], cf, "agreement_pdf", agreement.AgreementPdf, now) // Now, delete the original record. m = cassandra.NewMutation() m.Deletion = cassandra.NewDeletion() m.Deletion.Predicate = cassandra.NewSlicePredicate() m.Deletion.Predicate.ColumnNames = [][]byte{[]byte("pb_data")} m.Deletion.Timestamp = col.Timestamp mmap[string(ks.Key)] = make(map[string][]*cassandra.Mutation) mmap[string(ks.Key)]["membership_queue"] = []*cassandra.Mutation{m} // Write welcome e-mail to new member. if welcome != nil { err = welcome.SendMail(agreement.MemberData) if err != nil { log.Print("Error sending welcome e-mail to ", agreement.MemberData.GetEmail(), ": ", err) } } } // Apply all database mutations. err = db.BatchMutate(mmap, cassandra.ConsistencyLevel_QUORUM) if err != nil { log.Fatal("Error getting range slice: ", err) } } // Delete parting members. cp.ColumnFamily = "membership_dequeue" kr.StartKey = []byte("dequeue:") kr.EndKey = []byte("dequeue;") kss, err = db.GetRangeSlices(cp, pred, kr, cassandra.ConsistencyLevel_QUORUM) if err != nil { log.Fatal("Error getting range slice: ", err) } for _, ks = range kss { var csc *cassandra.ColumnOrSuperColumn mmap = make(map[string]map[string][]*cassandra.Mutation) for _, csc = range ks.Columns { var uuid cassandra.UUID var ldapuser string var col *cassandra.Column = csc.Column var agreement membersys.MembershipAgreement var attrs *ldap.ModifyRequest var m *cassandra.Mutation if col == nil { continue } if string(col.Name) != "pb_data" { log.Print("Column selected was not as requested: ", col.Name) continue } err = proto.Unmarshal(col.Value, &agreement) if err != nil { log.Print("Unable to parse column ", ks.Key, ": ", err) continue } if agreement.MemberData.Username == nil { continue } ldapuser = "******" + asciiFilter(agreement.MemberData.GetUsername()) + "," + config.LdapConfig.GetNewUserSuffix() + "," + config.LdapConfig.GetBase() if noop { log.Print("Would remove user ", ldapuser) } else { var groups []string var groups_differ bool var entry *ldap.Entry var group string sreq = ldap.NewSearchRequest( config.LdapConfig.GetBase(), ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1000, 90, false, "(&(objectClass=posixGroup)(memberUid="+ asciiFilter(agreement.MemberData.GetUsername())+"))", []string{"cn"}, []ldap.Control{}) lres, err = ld.Search(sreq) if err != nil { log.Print("Error searching groups for user ", agreement.MemberData.GetUsername(), ": ", err) continue } for _, entry = range lres.Entries { var cn string var found bool cn = entry.GetAttributeValue("cn") if cn == "" { log.Print("No group name set for ", agreement.MemberData.GetUsername()) continue } groups = append(groups, cn) for _, group = range config.LdapConfig.GetNewUserGroup() { if cn == group { found = true } } for _, group = range config.LdapConfig.GetIgnoreUserGroup() { if cn == group { found = true } } if !found { groups_differ = true } } if groups_differ { log.Print("User is in other groups than expected: ", strings.Join(groups, ", ")) for _, group = range config.LdapConfig.GetNewUserGroup() { attrs = ldap.NewModifyRequest("cn=" + group + ",ou=Groups," + config.LdapConfig.GetBase()) attrs.Delete("memberUid", []string{ asciiFilter(agreement.MemberData.GetUsername())}) err = ld.Modify(attrs) if err != nil { log.Print("Error deleting ", agreement.MemberData.GetUsername(), " from ", group, ": ", err) } } } else { var dr = ldap.NewDelRequest(ldapuser, []ldap.Control{}) // The user appears to be only in the groups given in // the config. err = ld.Del(dr) if err != nil { log.Print("Unable to delete user ", ldapuser, ": ", err) } } } m = cassandra.NewMutation() m.Deletion = cassandra.NewDeletion() m.Deletion.Predicate = pred m.Deletion.Timestamp = col.Timestamp mmap[string(ks.Key)] = make(map[string][]*cassandra.Mutation) mmap[string(ks.Key)]["membership_dequeue"] = []*cassandra.Mutation{m} // 2 years retention. col.TTL = proto.Int32(720 * 24 * 3600) col.Timestamp = proto.Int64(now.UnixNano()) uuid = cassandra.UUIDFromBytes(ks.Key[len("dequeue:"):]) m = cassandra.NewMutation() m.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn() m.ColumnOrSupercolumn.Column = col mmap["archive:"+string([]byte(uuid))] = make(map[string][]*cassandra.Mutation) mmap["archive:"+string([]byte(uuid))]["membership_archive"] = []*cassandra.Mutation{m} } // Apply all database mutations. err = db.BatchMutate(mmap, cassandra.ConsistencyLevel_QUORUM) if err != nil { log.Fatal("Error getting range slice: ", err) } } if verbose { log.Print("Greatest UID: ", greatestUid) } }
// Move a member record to the queue for getting their user account removed // (e.g. when they leave us). Set the retention to 2 years instead of just // 6 months, since they have been a member. func (m *MembershipDB) MoveMemberToTrash(id, initiator, reason string) error { var now time.Time = time.Now() var now_long uint64 = uint64(now.Unix()) var uuid cassandra.UUID var mmap map[string]map[string][]*cassandra.Mutation var member *MembershipAgreement var cp *cassandra.ColumnPath = cassandra.NewColumnPath() var cos *cassandra.ColumnOrSuperColumn var del *cassandra.Deletion = cassandra.NewDeletion() var mu *cassandra.Mutation var ts int64 var err error cp.ColumnFamily = "members" cp.Column = []byte("pb_data") uuid, err = cassandra.GenTimeUUID(&now) if err != nil { return err } cos, err = m.conn.Get( []byte(memberPrefix+id), cp, cassandra.ConsistencyLevel_QUORUM) if err != nil { return err } member = new(MembershipAgreement) err = proto.Unmarshal(cos.Column.Value, member) if err != nil { return err } del.Predicate = cassandra.NewSlicePredicate() del.Predicate.ColumnNames = allColumns del.Timestamp = cos.Column.Timestamp mu = cassandra.NewMutation() mu.Deletion = del mmap = make(map[string]map[string][]*cassandra.Mutation) mmap[memberPrefix+id] = make(map[string][]*cassandra.Mutation) mmap[memberPrefix+id]["members"] = []*cassandra.Mutation{mu} member.Metadata.GoodbyeInitiator = &initiator member.Metadata.GoodbyeTimestamp = &now_long member.Metadata.GoodbyeReason = &reason ts = now.UnixNano() cos.Column = cassandra.NewColumn() cos.Column.Name = []byte("pb_data") cos.Column.Timestamp = &ts cos.Column.Value, err = proto.Marshal(member) if err != nil { return err } mu = cassandra.NewMutation() mu.ColumnOrSupercolumn = cos mmap[dequeuePrefix+id] = make(map[string][]*cassandra.Mutation) mmap[dequeuePrefix+string([]byte(uuid))] = make(map[string][]*cassandra.Mutation) mmap[dequeuePrefix+string([]byte(uuid))]["membership_dequeue"] = []*cassandra.Mutation{mu} err = m.conn.AtomicBatchMutate( mmap, cassandra.ConsistencyLevel_QUORUM) return err }
// Get a list of all membership applications currently in the database. // Returns a set of "num" entries beginning after "prev". If "criterion" is // given, it will be compared against the name of the member. func (m *MembershipDB) EnumerateMembershipRequests(criterion, prev string, num int32) ( []*MemberWithKey, error) { var cp *cassandra.ColumnParent = cassandra.NewColumnParent() var pred *cassandra.SlicePredicate = cassandra.NewSlicePredicate() var r *cassandra.KeyRange = cassandra.NewKeyRange() var kss []*cassandra.KeySlice var ks *cassandra.KeySlice var rv []*MemberWithKey var err error // Fetch the name, street, city and fee columns of the application column family. cp.ColumnFamily = "application" pred.ColumnNames = [][]byte{ []byte("name"), []byte("street"), []byte("city"), []byte("fee"), []byte("fee_yearly"), } if len(prev) > 0 { var uuid cassandra.UUID if uuid, err = cassandra.ParseUUID(prev); err != nil { return rv, err } r.StartKey = append([]byte(applicationPrefix), []byte(uuid)...) } else { r.StartKey = []byte(applicationPrefix) } r.EndKey = []byte(applicationEnd) r.Count = num kss, err = m.conn.GetRangeSlices( cp, pred, r, cassandra.ConsistencyLevel_ONE) if err != nil { return rv, err } for _, ks = range kss { var member *MemberWithKey = new(MemberWithKey) var scol *cassandra.ColumnOrSuperColumn var uuid cassandra.UUID = cassandra.UUIDFromBytes( ks.Key[len(applicationPrefix):]) member.Key = uuid.String() if len(ks.Columns) == 0 { continue } for _, scol = range ks.Columns { var col *cassandra.Column = scol.Column if string(col.Name) == "name" { member.Name = proto.String(string(col.Value)) } else if string(col.Name) == "street" { member.Street = proto.String(string(col.Value)) } else if string(col.Name) == "city" { member.City = proto.String(string(col.Value)) } else if string(col.Name) == "fee" { member.Fee = proto.Uint64(binary.BigEndian.Uint64(col.Value)) } else if string(col.Name) == "fee_yearly" { member.FeeYearly = proto.Bool(col.Value[0] == 1) } } rv = append(rv, member) } return rv, nil }
// Get a list of all members currently in the database. Returns a set of // "num" entries beginning after "prev". // Returns a filled-out member structure and the timestamp when the // membership was approved. func (m *MembershipDB) EnumerateMembers(prev string, num int32) ( []*Member, error) { var cp *cassandra.ColumnParent = cassandra.NewColumnParent() var pred *cassandra.SlicePredicate = cassandra.NewSlicePredicate() var r *cassandra.KeyRange = cassandra.NewKeyRange() var kss []*cassandra.KeySlice var ks *cassandra.KeySlice var rv []*Member var err error // Fetch all relevant non-protobuf columns of the members column family. cp.ColumnFamily = "members" pred.ColumnNames = [][]byte{ []byte("name"), []byte("street"), []byte("city"), []byte("country"), []byte("email"), []byte("phone"), []byte("username"), []byte("fee"), []byte("fee_yearly"), } r.StartKey = []byte(memberPrefix + prev) r.EndKey = []byte(memberEnd) r.Count = num kss, err = m.conn.GetRangeSlices( cp, pred, r, cassandra.ConsistencyLevel_ONE) if err != nil { return rv, err } for _, ks = range kss { var member *Member = new(Member) var scol *cassandra.ColumnOrSuperColumn if len(ks.Columns) == 0 { continue } member.Email = proto.String(string(ks.Key[len(memberPrefix):])) for _, scol = range ks.Columns { var col *cassandra.Column = scol.Column var colname string = string(col.Name) if colname == "name" { member.Name = proto.String(string(col.Value)) } else if colname == "street" { member.Street = proto.String(string(col.Value)) } else if colname == "city" { member.City = proto.String(string(col.Value)) } else if colname == "country" { member.Country = proto.String(string(col.Value)) } else if colname == "email" { member.Email = proto.String(string(col.Value)) } else if colname == "phone" { member.Phone = proto.String(string(col.Value)) } else if colname == "username" { member.Username = proto.String(string(col.Value)) } else if colname == "fee" { member.Fee = proto.Uint64(binary.BigEndian.Uint64(col.Value)) } else if colname == "fee_yearly" { member.FeeYearly = proto.Bool(col.Value[0] == 1) } } rv = append(rv, member) } return rv, nil }