Example #1
0
// 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
}
Example #2
0
// 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 = &timestamp
	bmods[src_prefix+string(uuid)][src_table] = append(
		bmods[src_prefix+string(uuid)][src_table], mutation)

	return m.conn.AtomicBatchMutate(bmods, cassandra.ConsistencyLevel_QUORUM)
}
Example #3
0
// 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)
	}
}
Example #5
0
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)
	}
}
Example #7
0
// 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
}
Example #8
0
// 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
}
Example #9
0
// 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
}